1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Driver for CC770 and AN82527 CAN controllers on the platform bus |
4 | * |
5 | * Copyright (C) 2009, 2011 Wolfgang Grandegger <wg@grandegger.com> |
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" ); |
55 | MODULE_ALIAS("platform:" DRV_NAME); |
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 | |
70 | static int cc770_get_of_node_data(struct platform_device *pdev, |
71 | struct cc770_priv *priv) |
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(node: np, name: "bosch,external-clock-frequency" , |
79 | lenp: &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_property_read_bool(np, propname: "bosch,divide-memory-clock" )) |
97 | priv->cpu_interface |= CPUIF_DMC; |
98 | if (of_property_read_bool(np, propname: "bosch,iso-low-speed-mux" )) |
99 | priv->cpu_interface |= CPUIF_MUX; |
100 | |
101 | if (!of_get_property(node: np, name: "bosch,no-comperator-bypass" , NULL)) |
102 | priv->bus_config |= BUSCFG_CBY; |
103 | if (of_property_read_bool(np, propname: "bosch,disconnect-rx0-input" )) |
104 | priv->bus_config |= BUSCFG_DR0; |
105 | if (of_property_read_bool(np, propname: "bosch,disconnect-rx1-input" )) |
106 | priv->bus_config |= BUSCFG_DR1; |
107 | if (of_property_read_bool(np, propname: "bosch,disconnect-tx1-output" )) |
108 | priv->bus_config |= BUSCFG_DT1; |
109 | if (of_property_read_bool(np, propname: "bosch,polarity-dominant" )) |
110 | priv->bus_config |= BUSCFG_POL; |
111 | |
112 | prop = of_get_property(node: np, name: "bosch,clock-out-frequency" , lenp: &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(node: np, name: "bosch,slew-rate" , |
122 | lenp: &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 | |
143 | static int cc770_get_platform_data(struct platform_device *pdev, |
144 | struct cc770_priv *priv) |
145 | { |
146 | |
147 | struct cc770_platform_data *pdata = dev_get_platdata(dev: &pdev->dev); |
148 | |
149 | priv->can.clock.freq = pdata->osc_freq; |
150 | if (priv->cpu_interface & CPUIF_DSC) |
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 | |
159 | static int cc770_platform_probe(struct platform_device *pdev) |
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(res: mem); |
174 | if (!request_mem_region(mem->start, mem_size, pdev->name)) |
175 | return -EBUSY; |
176 | |
177 | base = ioremap(offset: mem->start, size: mem_size); |
178 | if (!base) { |
179 | err = -ENOMEM; |
180 | goto exit_release_mem; |
181 | } |
182 | |
183 | dev = alloc_cc770dev(sizeof_priv: 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); |
198 | else if (dev_get_platdata(dev: &pdev->dev)) |
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 | |
211 | platform_set_drvdata(pdev, data: dev); |
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(addr: base); |
227 | exit_release_mem: |
228 | release_mem_region(mem->start, mem_size); |
229 | |
230 | return err; |
231 | } |
232 | |
233 | static void cc770_platform_remove(struct platform_device *pdev) |
234 | { |
235 | struct net_device *dev = platform_get_drvdata(pdev); |
236 | struct cc770_priv *priv = netdev_priv(dev); |
237 | struct resource *mem; |
238 | |
239 | unregister_cc770dev(dev); |
240 | iounmap(addr: 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 | |
247 | static const struct of_device_id cc770_platform_table[] = { |
248 | {.compatible = "bosch,cc770" }, /* CC770 from Bosch */ |
249 | {.compatible = "intc,82527" }, /* AN82527 from Intel CP */ |
250 | {}, |
251 | }; |
252 | MODULE_DEVICE_TABLE(of, cc770_platform_table); |
253 | |
254 | static struct platform_driver cc770_platform_driver = { |
255 | .driver = { |
256 | .name = DRV_NAME, |
257 | .of_match_table = cc770_platform_table, |
258 | }, |
259 | .probe = cc770_platform_probe, |
260 | .remove_new = cc770_platform_remove, |
261 | }; |
262 | |
263 | module_platform_driver(cc770_platform_driver); |
264 | |