]>
Commit | Line | Data |
---|---|---|
e285e44d WG |
1 | /* |
2 | * Driver for CC770 and AN82527 CAN controllers on the platform bus | |
3 | * | |
4 | * Copyright (C) 2009, 2011 Wolfgang Grandegger <wg@grandegger.com> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the version 2 of the GNU General Public License | |
8 | * as published by the Free Software Foundation | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | */ | |
15 | ||
16 | /* | |
17 | * If platform data are used you should have similar definitions | |
18 | * in your board-specific code: | |
19 | * | |
20 | * static struct cc770_platform_data myboard_cc770_pdata = { | |
21 | * .osc_freq = 16000000, | |
22 | * .cir = 0x41, | |
23 | * .cor = 0x20, | |
24 | * .bcr = 0x40, | |
25 | * }; | |
26 | * | |
27 | * Please see include/linux/can/platform/cc770.h for description of | |
28 | * above fields. | |
29 | * | |
30 | * If the device tree is used, you need a CAN node definition in your | |
31 | * DTS file similar to: | |
32 | * | |
33 | * can@3,100 { | |
34 | * compatible = "bosch,cc770"; | |
35 | * reg = <3 0x100 0x80>; | |
36 | * interrupts = <2 0>; | |
37 | * interrupt-parent = <&mpic>; | |
38 | * bosch,external-clock-frequency = <16000000>; | |
39 | * }; | |
40 | * | |
41 | * See "Documentation/devicetree/bindings/net/can/cc770.txt" for further | |
42 | * information. | |
43 | */ | |
44 | ||
45 | #include <linux/kernel.h> | |
46 | #include <linux/module.h> | |
47 | #include <linux/interrupt.h> | |
48 | #include <linux/netdevice.h> | |
49 | #include <linux/delay.h> | |
50 | #include <linux/platform_device.h> | |
51 | #include <linux/of.h> | |
52 | #include <linux/can.h> | |
53 | #include <linux/can/dev.h> | |
54 | #include <linux/can/platform/cc770.h> | |
55 | ||
56 | #include "cc770.h" | |
57 | ||
58 | #define DRV_NAME "cc770_platform" | |
59 | ||
60 | MODULE_AUTHOR("Wolfgang Grandegger <wg@grandegger.com>"); | |
61 | MODULE_DESCRIPTION("Socket-CAN driver for CC770 on the platform bus"); | |
62 | MODULE_LICENSE("GPL v2"); | |
f190a50c | 63 | MODULE_ALIAS("platform:" DRV_NAME); |
e285e44d WG |
64 | |
65 | #define CC770_PLATFORM_CAN_CLOCK 16000000 | |
66 | ||
67 | static u8 cc770_platform_read_reg(const struct cc770_priv *priv, int reg) | |
68 | { | |
69 | return ioread8(priv->reg_base + reg); | |
70 | } | |
71 | ||
72 | static void cc770_platform_write_reg(const struct cc770_priv *priv, int reg, | |
73 | u8 val) | |
74 | { | |
75 | iowrite8(val, priv->reg_base + reg); | |
76 | } | |
77 | ||
3c8ac0f2 | 78 | static int cc770_get_of_node_data(struct platform_device *pdev, |
1dd06ae8 | 79 | struct cc770_priv *priv) |
e285e44d WG |
80 | { |
81 | struct device_node *np = pdev->dev.of_node; | |
82 | const u32 *prop; | |
83 | int prop_size; | |
84 | u32 clkext; | |
85 | ||
86 | prop = of_get_property(np, "bosch,external-clock-frequency", | |
87 | &prop_size); | |
88 | if (prop && (prop_size == sizeof(u32))) | |
89 | clkext = *prop; | |
90 | else | |
91 | clkext = CC770_PLATFORM_CAN_CLOCK; /* default */ | |
92 | priv->can.clock.freq = clkext; | |
93 | ||
94 | /* The system clock may not exceed 10 MHz */ | |
95 | if (priv->can.clock.freq > 10000000) { | |
96 | priv->cpu_interface |= CPUIF_DSC; | |
97 | priv->can.clock.freq /= 2; | |
98 | } | |
99 | ||
100 | /* The memory clock may not exceed 8 MHz */ | |
101 | if (priv->can.clock.freq > 8000000) | |
102 | priv->cpu_interface |= CPUIF_DMC; | |
103 | ||
104 | if (of_get_property(np, "bosch,divide-memory-clock", NULL)) | |
105 | priv->cpu_interface |= CPUIF_DMC; | |
106 | if (of_get_property(np, "bosch,iso-low-speed-mux", NULL)) | |
107 | priv->cpu_interface |= CPUIF_MUX; | |
108 | ||
109 | if (!of_get_property(np, "bosch,no-comperator-bypass", NULL)) | |
110 | priv->bus_config |= BUSCFG_CBY; | |
111 | if (of_get_property(np, "bosch,disconnect-rx0-input", NULL)) | |
112 | priv->bus_config |= BUSCFG_DR0; | |
113 | if (of_get_property(np, "bosch,disconnect-rx1-input", NULL)) | |
114 | priv->bus_config |= BUSCFG_DR1; | |
115 | if (of_get_property(np, "bosch,disconnect-tx1-output", NULL)) | |
116 | priv->bus_config |= BUSCFG_DT1; | |
117 | if (of_get_property(np, "bosch,polarity-dominant", NULL)) | |
118 | priv->bus_config |= BUSCFG_POL; | |
119 | ||
120 | prop = of_get_property(np, "bosch,clock-out-frequency", &prop_size); | |
121 | if (prop && (prop_size == sizeof(u32)) && *prop > 0) { | |
122 | u32 cdv = clkext / *prop; | |
123 | int slew; | |
124 | ||
125 | if (cdv > 0 && cdv < 16) { | |
126 | priv->cpu_interface |= CPUIF_CEN; | |
127 | priv->clkout |= (cdv - 1) & CLKOUT_CD_MASK; | |
128 | ||
129 | prop = of_get_property(np, "bosch,slew-rate", | |
130 | &prop_size); | |
131 | if (prop && (prop_size == sizeof(u32))) { | |
132 | slew = *prop; | |
133 | } else { | |
134 | /* Determine default slew rate */ | |
135 | slew = (CLKOUT_SL_MASK >> | |
136 | CLKOUT_SL_SHIFT) - | |
137 | ((cdv * clkext - 1) / 8000000); | |
138 | if (slew < 0) | |
139 | slew = 0; | |
140 | } | |
141 | priv->clkout |= (slew << CLKOUT_SL_SHIFT) & | |
142 | CLKOUT_SL_MASK; | |
143 | } else { | |
144 | dev_dbg(&pdev->dev, "invalid clock-out-frequency\n"); | |
145 | } | |
146 | } | |
147 | ||
148 | return 0; | |
149 | } | |
150 | ||
3c8ac0f2 | 151 | static int cc770_get_platform_data(struct platform_device *pdev, |
1dd06ae8 | 152 | struct cc770_priv *priv) |
e285e44d WG |
153 | { |
154 | ||
155 | struct cc770_platform_data *pdata = pdev->dev.platform_data; | |
156 | ||
157 | priv->can.clock.freq = pdata->osc_freq; | |
dc605dbd | 158 | if (priv->cpu_interface & CPUIF_DSC) |
e285e44d WG |
159 | priv->can.clock.freq /= 2; |
160 | priv->clkout = pdata->cor; | |
161 | priv->bus_config = pdata->bcr; | |
162 | priv->cpu_interface = pdata->cir; | |
163 | ||
164 | return 0; | |
165 | } | |
166 | ||
3c8ac0f2 | 167 | static int cc770_platform_probe(struct platform_device *pdev) |
e285e44d WG |
168 | { |
169 | struct net_device *dev; | |
170 | struct cc770_priv *priv; | |
171 | struct resource *mem; | |
172 | resource_size_t mem_size; | |
173 | void __iomem *base; | |
174 | int err, irq; | |
175 | ||
176 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
177 | irq = platform_get_irq(pdev, 0); | |
178 | if (!mem || irq <= 0) | |
179 | return -ENODEV; | |
180 | ||
181 | mem_size = resource_size(mem); | |
182 | if (!request_mem_region(mem->start, mem_size, pdev->name)) | |
183 | return -EBUSY; | |
184 | ||
185 | base = ioremap(mem->start, mem_size); | |
186 | if (!base) { | |
187 | err = -ENOMEM; | |
188 | goto exit_release_mem; | |
189 | } | |
190 | ||
191 | dev = alloc_cc770dev(0); | |
192 | if (!dev) { | |
193 | err = -ENOMEM; | |
194 | goto exit_unmap_mem; | |
195 | } | |
196 | ||
197 | dev->irq = irq; | |
198 | priv = netdev_priv(dev); | |
199 | priv->read_reg = cc770_platform_read_reg; | |
200 | priv->write_reg = cc770_platform_write_reg; | |
201 | priv->irq_flags = IRQF_SHARED; | |
202 | priv->reg_base = base; | |
203 | ||
204 | if (pdev->dev.of_node) | |
205 | err = cc770_get_of_node_data(pdev, priv); | |
206 | else if (pdev->dev.platform_data) | |
207 | err = cc770_get_platform_data(pdev, priv); | |
208 | else | |
209 | err = -ENODEV; | |
210 | if (err) | |
211 | goto exit_free_cc770; | |
212 | ||
213 | dev_dbg(&pdev->dev, | |
214 | "reg_base=0x%p irq=%d clock=%d cpu_interface=0x%02x " | |
215 | "bus_config=0x%02x clkout=0x%02x\n", | |
216 | priv->reg_base, dev->irq, priv->can.clock.freq, | |
217 | priv->cpu_interface, priv->bus_config, priv->clkout); | |
218 | ||
219 | dev_set_drvdata(&pdev->dev, dev); | |
220 | SET_NETDEV_DEV(dev, &pdev->dev); | |
221 | ||
222 | err = register_cc770dev(dev); | |
223 | if (err) { | |
224 | dev_err(&pdev->dev, | |
225 | "couldn't register CC700 device (err=%d)\n", err); | |
226 | goto exit_free_cc770; | |
227 | } | |
228 | ||
229 | return 0; | |
230 | ||
231 | exit_free_cc770: | |
232 | free_cc770dev(dev); | |
233 | exit_unmap_mem: | |
234 | iounmap(base); | |
235 | exit_release_mem: | |
236 | release_mem_region(mem->start, mem_size); | |
237 | ||
238 | return err; | |
239 | } | |
240 | ||
3c8ac0f2 | 241 | static int cc770_platform_remove(struct platform_device *pdev) |
e285e44d WG |
242 | { |
243 | struct net_device *dev = dev_get_drvdata(&pdev->dev); | |
244 | struct cc770_priv *priv = netdev_priv(dev); | |
245 | struct resource *mem; | |
246 | ||
247 | unregister_cc770dev(dev); | |
248 | iounmap(priv->reg_base); | |
249 | free_cc770dev(dev); | |
250 | ||
251 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
252 | release_mem_region(mem->start, resource_size(mem)); | |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
3c8ac0f2 | 257 | static struct of_device_id cc770_platform_table[] = { |
e285e44d WG |
258 | {.compatible = "bosch,cc770"}, /* CC770 from Bosch */ |
259 | {.compatible = "intc,82527"}, /* AN82527 from Intel CP */ | |
260 | {}, | |
261 | }; | |
f190a50c | 262 | MODULE_DEVICE_TABLE(of, cc770_platform_table); |
e285e44d WG |
263 | |
264 | static struct platform_driver cc770_platform_driver = { | |
265 | .driver = { | |
266 | .name = DRV_NAME, | |
267 | .owner = THIS_MODULE, | |
268 | .of_match_table = cc770_platform_table, | |
269 | }, | |
270 | .probe = cc770_platform_probe, | |
3c8ac0f2 | 271 | .remove = cc770_platform_remove, |
e285e44d WG |
272 | }; |
273 | ||
274 | module_platform_driver(cc770_platform_driver); |