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