Blame view

drivers/misc/atmel_tclib.c 4.65 KB
457c89965   Thomas Gleixner   treewide: Add SPD...
1
  // SPDX-License-Identifier: GPL-2.0-only
2a341f5cf   David Brownell   atmel_tc library
2
3
4
5
6
7
8
  #include <linux/clk.h>
  #include <linux/err.h>
  #include <linux/init.h>
  #include <linux/io.h>
  #include <linux/ioport.h>
  #include <linux/kernel.h>
  #include <linux/platform_device.h>
3a61a5dae   Nicolas Ferre   ARM: at91/tc: add...
9
  #include <linux/module.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
10
  #include <linux/slab.h>
7a32129a4   Paul Gortmaker   drivers/misc: Add...
11
  #include <linux/export.h>
3a61a5dae   Nicolas Ferre   ARM: at91/tc: add...
12
  #include <linux/of.h>
c2c9136b7   Alexandre Belloni   ARM: at91: move S...
13
  #include <soc/at91/atmel_tcb.h>
2a341f5cf   David Brownell   atmel_tc library
14

2a341f5cf   David Brownell   atmel_tc library
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  /*
   * This is a thin library to solve the problem of how to portably allocate
   * one of the TC blocks.  For simplicity, it doesn't currently expect to
   * share individual timers between different drivers.
   */
  
  #if defined(CONFIG_AVR32)
  /* AVR32 has these divide PBB */
  const u8 atmel_tc_divisors[5] = { 0, 4, 8, 16, 32, };
  EXPORT_SYMBOL(atmel_tc_divisors);
  
  #elif defined(CONFIG_ARCH_AT91)
  /* AT91 has these divide MCK */
  const u8 atmel_tc_divisors[5] = { 2, 8, 32, 128, 0, };
  EXPORT_SYMBOL(atmel_tc_divisors);
  
  #endif
  
  static DEFINE_SPINLOCK(tc_list_lock);
  static LIST_HEAD(tc_list);
  
  /**
   * atmel_tc_alloc - allocate a specified TC block
   * @block: which block to allocate
2a341f5cf   David Brownell   atmel_tc library
39
40
41
42
43
   *
   * Caller allocates a block.  If it is available, a pointer to a
   * pre-initialized struct atmel_tc is returned. The caller can access
   * the registers directly through the "regs" field.
   */
4930d247a   Gaël PORTAY   ARM: at91/tclib: ...
44
  struct atmel_tc *atmel_tc_alloc(unsigned block)
2a341f5cf   David Brownell   atmel_tc library
45
46
47
  {
  	struct atmel_tc		*tc;
  	struct platform_device	*pdev = NULL;
2a341f5cf   David Brownell   atmel_tc library
48
49
50
  
  	spin_lock(&tc_list_lock);
  	list_for_each_entry(tc, &tc_list, node) {
4930d247a   Gaël PORTAY   ARM: at91/tclib: ...
51
52
53
54
55
  		if (tc->allocated)
  			continue;
  
  		if ((tc->pdev->dev.of_node && tc->id == block) ||
  		    (tc->pdev->id == block)) {
2a341f5cf   David Brownell   atmel_tc library
56
  			pdev = tc->pdev;
4930d247a   Gaël PORTAY   ARM: at91/tclib: ...
57
  			tc->allocated = true;
2a341f5cf   David Brownell   atmel_tc library
58
59
60
  			break;
  		}
  	}
2a341f5cf   David Brownell   atmel_tc library
61
  	spin_unlock(&tc_list_lock);
2a341f5cf   David Brownell   atmel_tc library
62

4930d247a   Gaël PORTAY   ARM: at91/tclib: ...
63
  	return pdev ? tc : NULL;
2a341f5cf   David Brownell   atmel_tc library
64
65
66
67
68
69
70
  }
  EXPORT_SYMBOL_GPL(atmel_tc_alloc);
  
  /**
   * atmel_tc_free - release a specified TC block
   * @tc: Timer/counter block that was returned by atmel_tc_alloc()
   *
4930d247a   Gaël PORTAY   ARM: at91/tclib: ...
71
72
   * This reverses the effect of atmel_tc_alloc(), invalidating the resource
   * returned by that routine and making the TC available to other drivers.
2a341f5cf   David Brownell   atmel_tc library
73
74
75
76
   */
  void atmel_tc_free(struct atmel_tc *tc)
  {
  	spin_lock(&tc_list_lock);
4930d247a   Gaël PORTAY   ARM: at91/tclib: ...
77
78
  	if (tc->allocated)
  		tc->allocated = false;
2a341f5cf   David Brownell   atmel_tc library
79
80
81
  	spin_unlock(&tc_list_lock);
  }
  EXPORT_SYMBOL_GPL(atmel_tc_free);
3a61a5dae   Nicolas Ferre   ARM: at91/tc: add...
82
  #if defined(CONFIG_OF)
8e315a7b0   Nicolas Ferre   ARM: at91/tc/cloc...
83
84
85
86
87
88
89
  static struct atmel_tcb_config tcb_rm9200_config = {
  	.counter_width = 16,
  };
  
  static struct atmel_tcb_config tcb_sam9x5_config = {
  	.counter_width = 32,
  };
3a61a5dae   Nicolas Ferre   ARM: at91/tc: add...
90
91
92
  static const struct of_device_id atmel_tcb_dt_ids[] = {
  	{
  		.compatible = "atmel,at91rm9200-tcb",
8e315a7b0   Nicolas Ferre   ARM: at91/tc/cloc...
93
94
95
96
  		.data = &tcb_rm9200_config,
  	}, {
  		.compatible = "atmel,at91sam9x5-tcb",
  		.data = &tcb_sam9x5_config,
3a61a5dae   Nicolas Ferre   ARM: at91/tc: add...
97
98
99
100
101
102
103
  	}, {
  		/* sentinel */
  	}
  };
  
  MODULE_DEVICE_TABLE(of, atmel_tcb_dt_ids);
  #endif
2a341f5cf   David Brownell   atmel_tc library
104
105
106
107
108
  static int __init tc_probe(struct platform_device *pdev)
  {
  	struct atmel_tc *tc;
  	struct clk	*clk;
  	int		irq;
4930d247a   Gaël PORTAY   ARM: at91/tclib: ...
109
  	struct resource	*r;
84f462371   Gaël PORTAY   ARM: at91/tclib: ...
110
  	unsigned int	i;
2a341f5cf   David Brownell   atmel_tc library
111

8c9374068   Alexandre Belloni   misc: atmel_tclib...
112
113
  	if (of_get_child_count(pdev->dev.of_node))
  		return -EBUSY;
2a341f5cf   David Brownell   atmel_tc library
114
115
116
  	irq = platform_get_irq(pdev, 0);
  	if (irq < 0)
  		return -EINVAL;
8495497f9   Gaël PORTAY   ARM: at91/tclib: ...
117
  	tc = devm_kzalloc(&pdev->dev, sizeof(struct atmel_tc), GFP_KERNEL);
2a341f5cf   David Brownell   atmel_tc library
118
119
120
121
  	if (!tc)
  		return -ENOMEM;
  
  	tc->pdev = pdev;
8495497f9   Gaël PORTAY   ARM: at91/tclib: ...
122
123
124
  	clk = devm_clk_get(&pdev->dev, "t0_clk");
  	if (IS_ERR(clk))
  		return PTR_ERR(clk);
2a341f5cf   David Brownell   atmel_tc library
125

7d8d05d11   Boris Brezillon   misc: atmel_tclib...
126
127
128
  	tc->slow_clk = devm_clk_get(&pdev->dev, "slow_clk");
  	if (IS_ERR(tc->slow_clk))
  		return PTR_ERR(tc->slow_clk);
4930d247a   Gaël PORTAY   ARM: at91/tclib: ...
129
130
131
132
  	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  	tc->regs = devm_ioremap_resource(&pdev->dev, r);
  	if (IS_ERR(tc->regs))
  		return PTR_ERR(tc->regs);
8e315a7b0   Nicolas Ferre   ARM: at91/tc/cloc...
133
134
135
136
137
138
  	/* Now take SoC information if available */
  	if (pdev->dev.of_node) {
  		const struct of_device_id *match;
  		match = of_match_node(atmel_tcb_dt_ids, pdev->dev.of_node);
  		if (match)
  			tc->tcb_config = match->data;
4930d247a   Gaël PORTAY   ARM: at91/tclib: ...
139
140
141
142
  
  		tc->id = of_alias_get_id(tc->pdev->dev.of_node, "tcb");
  	} else {
  		tc->id = pdev->id;
8e315a7b0   Nicolas Ferre   ARM: at91/tc/cloc...
143
  	}
2a341f5cf   David Brownell   atmel_tc library
144
  	tc->clk[0] = clk;
8495497f9   Gaël PORTAY   ARM: at91/tclib: ...
145
  	tc->clk[1] = devm_clk_get(&pdev->dev, "t1_clk");
2a341f5cf   David Brownell   atmel_tc library
146
147
  	if (IS_ERR(tc->clk[1]))
  		tc->clk[1] = clk;
8495497f9   Gaël PORTAY   ARM: at91/tclib: ...
148
  	tc->clk[2] = devm_clk_get(&pdev->dev, "t2_clk");
2a341f5cf   David Brownell   atmel_tc library
149
150
151
152
153
154
155
156
157
158
  	if (IS_ERR(tc->clk[2]))
  		tc->clk[2] = clk;
  
  	tc->irq[0] = irq;
  	tc->irq[1] = platform_get_irq(pdev, 1);
  	if (tc->irq[1] < 0)
  		tc->irq[1] = irq;
  	tc->irq[2] = platform_get_irq(pdev, 2);
  	if (tc->irq[2] < 0)
  		tc->irq[2] = irq;
84f462371   Gaël PORTAY   ARM: at91/tclib: ...
159
160
  	for (i = 0; i < 3; i++)
  		writel(ATMEL_TC_ALL_IRQ, tc->regs + ATMEL_TC_REG(i, IDR));
2a341f5cf   David Brownell   atmel_tc library
161
162
163
  	spin_lock(&tc_list_lock);
  	list_add_tail(&tc->node, &tc_list);
  	spin_unlock(&tc_list_lock);
84f462371   Gaël PORTAY   ARM: at91/tclib: ...
164
  	platform_set_drvdata(pdev, tc);
2a341f5cf   David Brownell   atmel_tc library
165
166
  	return 0;
  }
84f462371   Gaël PORTAY   ARM: at91/tclib: ...
167
168
169
170
171
172
173
174
  static void tc_shutdown(struct platform_device *pdev)
  {
  	int i;
  	struct atmel_tc *tc = platform_get_drvdata(pdev);
  
  	for (i = 0; i < 3; i++)
  		writel(ATMEL_TC_ALL_IRQ, tc->regs + ATMEL_TC_REG(i, IDR));
  }
2a341f5cf   David Brownell   atmel_tc library
175
  static struct platform_driver tc_driver = {
3a61a5dae   Nicolas Ferre   ARM: at91/tc: add...
176
177
178
179
  	.driver = {
  		.name	= "atmel_tcb",
  		.of_match_table	= of_match_ptr(atmel_tcb_dt_ids),
  	},
84f462371   Gaël PORTAY   ARM: at91/tclib: ...
180
  	.shutdown = tc_shutdown,
2a341f5cf   David Brownell   atmel_tc library
181
182
183
184
185
186
187
  };
  
  static int __init tc_init(void)
  {
  	return platform_driver_probe(&tc_driver, tc_probe);
  }
  arch_initcall(tc_init);