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"); | |
63 | ||
64 | #define CC770_PLATFORM_CAN_CLOCK 16000000 | |
65 | ||
66 | static u8 cc770_platform_read_reg(const struct cc770_priv *priv, int reg) | |
67 | { | |
68 | return ioread8(priv->reg_base + reg); | |
69 | } | |
70 | ||
71 | static void cc770_platform_write_reg(const struct cc770_priv *priv, int reg, | |
72 | u8 val) | |
73 | { | |
74 | iowrite8(val, priv->reg_base + reg); | |
75 | } | |
76 | ||
77 | static int __devinit cc770_get_of_node_data(struct platform_device *pdev, | |
78 | struct cc770_priv *priv) | |
79 | { | |
80 | struct device_node *np = pdev->dev.of_node; | |
81 | const u32 *prop; | |
82 | int prop_size; | |
83 | u32 clkext; | |
84 | ||
85 | prop = of_get_property(np, "bosch,external-clock-frequency", | |
86 | &prop_size); | |
87 | if (prop && (prop_size == sizeof(u32))) | |
88 | clkext = *prop; | |
89 | else | |
90 | clkext = CC770_PLATFORM_CAN_CLOCK; /* default */ | |
91 | priv->can.clock.freq = clkext; | |
92 | ||
93 | /* The system clock may not exceed 10 MHz */ | |
94 | if (priv->can.clock.freq > 10000000) { | |
95 | priv->cpu_interface |= CPUIF_DSC; | |
96 | priv->can.clock.freq /= 2; | |
97 | } | |
98 | ||
99 | /* The memory clock may not exceed 8 MHz */ | |
100 | if (priv->can.clock.freq > 8000000) | |
101 | priv->cpu_interface |= CPUIF_DMC; | |
102 | ||
103 | if (of_get_property(np, "bosch,divide-memory-clock", NULL)) | |
104 | priv->cpu_interface |= CPUIF_DMC; | |
105 | if (of_get_property(np, "bosch,iso-low-speed-mux", NULL)) | |
106 | priv->cpu_interface |= CPUIF_MUX; | |
107 | ||
108 | if (!of_get_property(np, "bosch,no-comperator-bypass", NULL)) | |
109 | priv->bus_config |= BUSCFG_CBY; | |
110 | if (of_get_property(np, "bosch,disconnect-rx0-input", NULL)) | |
111 | priv->bus_config |= BUSCFG_DR0; | |
112 | if (of_get_property(np, "bosch,disconnect-rx1-input", NULL)) | |
113 | priv->bus_config |= BUSCFG_DR1; | |
114 | if (of_get_property(np, "bosch,disconnect-tx1-output", NULL)) | |
115 | priv->bus_config |= BUSCFG_DT1; | |
116 | if (of_get_property(np, "bosch,polarity-dominant", NULL)) | |
117 | priv->bus_config |= BUSCFG_POL; | |
118 | ||
119 | prop = of_get_property(np, "bosch,clock-out-frequency", &prop_size); | |
120 | if (prop && (prop_size == sizeof(u32)) && *prop > 0) { | |
121 | u32 cdv = clkext / *prop; | |
122 | int slew; | |
123 | ||
124 | if (cdv > 0 && cdv < 16) { | |
125 | priv->cpu_interface |= CPUIF_CEN; | |
126 | priv->clkout |= (cdv - 1) & CLKOUT_CD_MASK; | |
127 | ||
128 | prop = of_get_property(np, "bosch,slew-rate", | |
129 | &prop_size); | |
130 | if (prop && (prop_size == sizeof(u32))) { | |
131 | slew = *prop; | |
132 | } else { | |
133 | /* Determine default slew rate */ | |
134 | slew = (CLKOUT_SL_MASK >> | |
135 | CLKOUT_SL_SHIFT) - | |
136 | ((cdv * clkext - 1) / 8000000); | |
137 | if (slew < 0) | |
138 | slew = 0; | |
139 | } | |
140 | priv->clkout |= (slew << CLKOUT_SL_SHIFT) & | |
141 | CLKOUT_SL_MASK; | |
142 | } else { | |
143 | dev_dbg(&pdev->dev, "invalid clock-out-frequency\n"); | |
144 | } | |
145 | } | |
146 | ||
147 | return 0; | |
148 | } | |
149 | ||
150 | static int __devinit cc770_get_platform_data(struct platform_device *pdev, | |
151 | struct cc770_priv *priv) | |
152 | { | |
153 | ||
154 | struct cc770_platform_data *pdata = pdev->dev.platform_data; | |
155 | ||
156 | priv->can.clock.freq = pdata->osc_freq; | |
dc605dbd | 157 | if (priv->cpu_interface & CPUIF_DSC) |
e285e44d WG |
158 | priv->can.clock.freq /= 2; |
159 | priv->clkout = pdata->cor; | |
160 | priv->bus_config = pdata->bcr; | |
161 | priv->cpu_interface = pdata->cir; | |
162 | ||
163 | return 0; | |
164 | } | |
165 | ||
166 | static int __devinit cc770_platform_probe(struct platform_device *pdev) | |
167 | { | |
168 | struct net_device *dev; | |
169 | struct cc770_priv *priv; | |
170 | struct resource *mem; | |
171 | resource_size_t mem_size; | |
172 | void __iomem *base; | |
173 | int err, irq; | |
174 | ||
175 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
176 | irq = platform_get_irq(pdev, 0); | |
177 | if (!mem || irq <= 0) | |
178 | return -ENODEV; | |
179 | ||
180 | mem_size = resource_size(mem); | |
181 | if (!request_mem_region(mem->start, mem_size, pdev->name)) | |
182 | return -EBUSY; | |
183 | ||
184 | base = ioremap(mem->start, mem_size); | |
185 | if (!base) { | |
186 | err = -ENOMEM; | |
187 | goto exit_release_mem; | |
188 | } | |
189 | ||
190 | dev = alloc_cc770dev(0); | |
191 | if (!dev) { | |
192 | err = -ENOMEM; | |
193 | goto exit_unmap_mem; | |
194 | } | |
195 | ||
196 | dev->irq = irq; | |
197 | priv = netdev_priv(dev); | |
198 | priv->read_reg = cc770_platform_read_reg; | |
199 | priv->write_reg = cc770_platform_write_reg; | |
200 | priv->irq_flags = IRQF_SHARED; | |
201 | priv->reg_base = base; | |
202 | ||
203 | if (pdev->dev.of_node) | |
204 | err = cc770_get_of_node_data(pdev, priv); | |
205 | else if (pdev->dev.platform_data) | |
206 | err = cc770_get_platform_data(pdev, priv); | |
207 | else | |
208 | err = -ENODEV; | |
209 | if (err) | |
210 | goto exit_free_cc770; | |
211 | ||
212 | dev_dbg(&pdev->dev, | |
213 | "reg_base=0x%p irq=%d clock=%d cpu_interface=0x%02x " | |
214 | "bus_config=0x%02x clkout=0x%02x\n", | |
215 | priv->reg_base, dev->irq, priv->can.clock.freq, | |
216 | priv->cpu_interface, priv->bus_config, priv->clkout); | |
217 | ||
218 | dev_set_drvdata(&pdev->dev, dev); | |
219 | SET_NETDEV_DEV(dev, &pdev->dev); | |
220 | ||
221 | err = register_cc770dev(dev); | |
222 | if (err) { | |
223 | dev_err(&pdev->dev, | |
224 | "couldn't register CC700 device (err=%d)\n", err); | |
225 | goto exit_free_cc770; | |
226 | } | |
227 | ||
228 | return 0; | |
229 | ||
230 | exit_free_cc770: | |
231 | free_cc770dev(dev); | |
232 | exit_unmap_mem: | |
233 | iounmap(base); | |
234 | exit_release_mem: | |
235 | release_mem_region(mem->start, mem_size); | |
236 | ||
237 | return err; | |
238 | } | |
239 | ||
240 | static int __devexit cc770_platform_remove(struct platform_device *pdev) | |
241 | { | |
242 | struct net_device *dev = dev_get_drvdata(&pdev->dev); | |
243 | struct cc770_priv *priv = netdev_priv(dev); | |
244 | struct resource *mem; | |
245 | ||
246 | unregister_cc770dev(dev); | |
247 | iounmap(priv->reg_base); | |
248 | free_cc770dev(dev); | |
249 | ||
250 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
251 | release_mem_region(mem->start, resource_size(mem)); | |
252 | ||
253 | return 0; | |
254 | } | |
255 | ||
256 | static struct of_device_id __devinitdata cc770_platform_table[] = { | |
257 | {.compatible = "bosch,cc770"}, /* CC770 from Bosch */ | |
258 | {.compatible = "intc,82527"}, /* AN82527 from Intel CP */ | |
259 | {}, | |
260 | }; | |
261 | ||
262 | static struct platform_driver cc770_platform_driver = { | |
263 | .driver = { | |
264 | .name = DRV_NAME, | |
265 | .owner = THIS_MODULE, | |
266 | .of_match_table = cc770_platform_table, | |
267 | }, | |
268 | .probe = cc770_platform_probe, | |
269 | .remove = __devexit_p(cc770_platform_remove), | |
270 | }; | |
271 | ||
272 | module_platform_driver(cc770_platform_driver); |