1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Driver for CC770 and AN82527 CAN controllers on the legacy ISA bus |
4 | * |
5 | * Copyright (C) 2009, 2011 Wolfgang Grandegger <wg@grandegger.com> |
6 | */ |
7 | |
8 | /* |
9 | * Bosch CC770 and Intel AN82527 CAN controllers on the ISA or PC-104 bus. |
10 | * The I/O port or memory address and the IRQ number must be specified via |
11 | * module parameters: |
12 | * |
13 | * insmod cc770_isa.ko port=0x310,0x380 irq=7,11 |
14 | * |
15 | * for ISA devices using I/O ports or: |
16 | * |
17 | * insmod cc770_isa.ko mem=0xd1000,0xd1000 irq=7,11 |
18 | * |
19 | * for memory mapped ISA devices. |
20 | * |
21 | * Indirect access via address and data port is supported as well: |
22 | * |
23 | * insmod cc770_isa.ko port=0x310,0x380 indirect=1 irq=7,11 |
24 | * |
25 | * Furthermore, the following mode parameter can be defined: |
26 | * |
27 | * clk: External oscillator clock frequency (default=16000000 [16 MHz]) |
28 | * cir: CPU interface register (default=0x40 [DSC]) |
29 | * bcr: Bus configuration register (default=0x40 [CBY]) |
30 | * cor: Clockout register (default=0x00) |
31 | * |
32 | * Note: for clk, cir, bcr and cor, the first argument re-defines the |
33 | * default for all other devices, e.g.: |
34 | * |
35 | * insmod cc770_isa.ko mem=0xd1000,0xd1000 irq=7,11 clk=24000000 |
36 | * |
37 | * is equivalent to |
38 | * |
39 | * insmod cc770_isa.ko mem=0xd1000,0xd1000 irq=7,11 clk=24000000,24000000 |
40 | */ |
41 | |
42 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
43 | |
44 | #include <linux/kernel.h> |
45 | #include <linux/module.h> |
46 | #include <linux/platform_device.h> |
47 | #include <linux/interrupt.h> |
48 | #include <linux/netdevice.h> |
49 | #include <linux/delay.h> |
50 | #include <linux/irq.h> |
51 | #include <linux/io.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 MAXDEV 8 |
59 | |
60 | MODULE_AUTHOR("Wolfgang Grandegger <wg@grandegger.com>" ); |
61 | MODULE_DESCRIPTION("Socket-CAN driver for CC770 on the ISA bus" ); |
62 | MODULE_LICENSE("GPL v2" ); |
63 | |
64 | #define CLK_DEFAULT 16000000 /* 16 MHz */ |
65 | #define COR_DEFAULT 0x00 |
66 | #define BCR_DEFAULT BUSCFG_CBY |
67 | |
68 | static unsigned long port[MAXDEV]; |
69 | static unsigned long mem[MAXDEV]; |
70 | static int irq[MAXDEV]; |
71 | static int clk[MAXDEV]; |
72 | static u8 cir[MAXDEV] = {[0 ... (MAXDEV - 1)] = 0xff}; |
73 | static u8 cor[MAXDEV] = {[0 ... (MAXDEV - 1)] = 0xff}; |
74 | static u8 bcr[MAXDEV] = {[0 ... (MAXDEV - 1)] = 0xff}; |
75 | static int indirect[MAXDEV] = {[0 ... (MAXDEV - 1)] = -1}; |
76 | |
77 | module_param_hw_array(port, ulong, ioport, NULL, 0444); |
78 | MODULE_PARM_DESC(port, "I/O port number" ); |
79 | |
80 | module_param_hw_array(mem, ulong, iomem, NULL, 0444); |
81 | MODULE_PARM_DESC(mem, "I/O memory address" ); |
82 | |
83 | module_param_hw_array(indirect, int, ioport, NULL, 0444); |
84 | MODULE_PARM_DESC(indirect, "Indirect access via address and data port" ); |
85 | |
86 | module_param_hw_array(irq, int, irq, NULL, 0444); |
87 | MODULE_PARM_DESC(irq, "IRQ number" ); |
88 | |
89 | module_param_array(clk, int, NULL, 0444); |
90 | MODULE_PARM_DESC(clk, "External oscillator clock frequency " |
91 | "(default=16000000 [16 MHz])" ); |
92 | |
93 | module_param_array(cir, byte, NULL, 0444); |
94 | MODULE_PARM_DESC(cir, "CPU interface register (default=0x40 [DSC])" ); |
95 | |
96 | module_param_array(cor, byte, NULL, 0444); |
97 | MODULE_PARM_DESC(cor, "Clockout register (default=0x00)" ); |
98 | |
99 | module_param_array(bcr, byte, NULL, 0444); |
100 | MODULE_PARM_DESC(bcr, "Bus configuration register (default=0x40 [CBY])" ); |
101 | |
102 | #define CC770_IOSIZE 0x20 |
103 | #define CC770_IOSIZE_INDIRECT 0x02 |
104 | |
105 | /* Spinlock for cc770_isa_port_write_reg_indirect |
106 | * and cc770_isa_port_read_reg_indirect |
107 | */ |
108 | static DEFINE_SPINLOCK(cc770_isa_port_lock); |
109 | |
110 | static struct platform_device *cc770_isa_devs[MAXDEV]; |
111 | |
112 | static u8 cc770_isa_mem_read_reg(const struct cc770_priv *priv, int reg) |
113 | { |
114 | return readb(addr: priv->reg_base + reg); |
115 | } |
116 | |
117 | static void cc770_isa_mem_write_reg(const struct cc770_priv *priv, |
118 | int reg, u8 val) |
119 | { |
120 | writeb(val, addr: priv->reg_base + reg); |
121 | } |
122 | |
123 | static u8 cc770_isa_port_read_reg(const struct cc770_priv *priv, int reg) |
124 | { |
125 | return inb(port: (unsigned long)priv->reg_base + reg); |
126 | } |
127 | |
128 | static void cc770_isa_port_write_reg(const struct cc770_priv *priv, |
129 | int reg, u8 val) |
130 | { |
131 | outb(value: val, port: (unsigned long)priv->reg_base + reg); |
132 | } |
133 | |
134 | static u8 cc770_isa_port_read_reg_indirect(const struct cc770_priv *priv, |
135 | int reg) |
136 | { |
137 | unsigned long base = (unsigned long)priv->reg_base; |
138 | unsigned long flags; |
139 | u8 val; |
140 | |
141 | spin_lock_irqsave(&cc770_isa_port_lock, flags); |
142 | outb(value: reg, port: base); |
143 | val = inb(port: base + 1); |
144 | spin_unlock_irqrestore(lock: &cc770_isa_port_lock, flags); |
145 | |
146 | return val; |
147 | } |
148 | |
149 | static void cc770_isa_port_write_reg_indirect(const struct cc770_priv *priv, |
150 | int reg, u8 val) |
151 | { |
152 | unsigned long base = (unsigned long)priv->reg_base; |
153 | unsigned long flags; |
154 | |
155 | spin_lock_irqsave(&cc770_isa_port_lock, flags); |
156 | outb(value: reg, port: base); |
157 | outb(value: val, port: base + 1); |
158 | spin_unlock_irqrestore(lock: &cc770_isa_port_lock, flags); |
159 | } |
160 | |
161 | static int cc770_isa_probe(struct platform_device *pdev) |
162 | { |
163 | struct net_device *dev; |
164 | struct cc770_priv *priv; |
165 | void __iomem *base = NULL; |
166 | int iosize = CC770_IOSIZE; |
167 | int idx = pdev->id; |
168 | int err; |
169 | u32 clktmp; |
170 | |
171 | dev_dbg(&pdev->dev, "probing idx=%d: port=%#lx, mem=%#lx, irq=%d\n" , |
172 | idx, port[idx], mem[idx], irq[idx]); |
173 | if (mem[idx]) { |
174 | if (!request_mem_region(mem[idx], iosize, KBUILD_MODNAME)) { |
175 | err = -EBUSY; |
176 | goto exit; |
177 | } |
178 | base = ioremap(offset: mem[idx], size: iosize); |
179 | if (!base) { |
180 | err = -ENOMEM; |
181 | goto exit_release; |
182 | } |
183 | } else { |
184 | if (indirect[idx] > 0 || |
185 | (indirect[idx] == -1 && indirect[0] > 0)) |
186 | iosize = CC770_IOSIZE_INDIRECT; |
187 | if (!request_region(port[idx], iosize, KBUILD_MODNAME)) { |
188 | err = -EBUSY; |
189 | goto exit; |
190 | } |
191 | } |
192 | |
193 | dev = alloc_cc770dev(sizeof_priv: 0); |
194 | if (!dev) { |
195 | err = -ENOMEM; |
196 | goto exit_unmap; |
197 | } |
198 | priv = netdev_priv(dev); |
199 | |
200 | dev->irq = irq[idx]; |
201 | priv->irq_flags = IRQF_SHARED; |
202 | if (mem[idx]) { |
203 | priv->reg_base = base; |
204 | dev->base_addr = mem[idx]; |
205 | priv->read_reg = cc770_isa_mem_read_reg; |
206 | priv->write_reg = cc770_isa_mem_write_reg; |
207 | } else { |
208 | priv->reg_base = (void __iomem *)port[idx]; |
209 | dev->base_addr = port[idx]; |
210 | |
211 | if (iosize == CC770_IOSIZE_INDIRECT) { |
212 | priv->read_reg = cc770_isa_port_read_reg_indirect; |
213 | priv->write_reg = cc770_isa_port_write_reg_indirect; |
214 | } else { |
215 | priv->read_reg = cc770_isa_port_read_reg; |
216 | priv->write_reg = cc770_isa_port_write_reg; |
217 | } |
218 | } |
219 | |
220 | if (clk[idx]) |
221 | clktmp = clk[idx]; |
222 | else if (clk[0]) |
223 | clktmp = clk[0]; |
224 | else |
225 | clktmp = CLK_DEFAULT; |
226 | priv->can.clock.freq = clktmp; |
227 | |
228 | if (cir[idx] != 0xff) { |
229 | priv->cpu_interface = cir[idx]; |
230 | } else if (cir[0] != 0xff) { |
231 | priv->cpu_interface = cir[0]; |
232 | } else { |
233 | /* The system clock may not exceed 10 MHz */ |
234 | if (clktmp > 10000000) { |
235 | priv->cpu_interface |= CPUIF_DSC; |
236 | clktmp /= 2; |
237 | } |
238 | /* The memory clock may not exceed 8 MHz */ |
239 | if (clktmp > 8000000) |
240 | priv->cpu_interface |= CPUIF_DMC; |
241 | } |
242 | |
243 | if (priv->cpu_interface & CPUIF_DSC) |
244 | priv->can.clock.freq /= 2; |
245 | |
246 | if (bcr[idx] != 0xff) |
247 | priv->bus_config = bcr[idx]; |
248 | else if (bcr[0] != 0xff) |
249 | priv->bus_config = bcr[0]; |
250 | else |
251 | priv->bus_config = BCR_DEFAULT; |
252 | |
253 | if (cor[idx] != 0xff) |
254 | priv->clkout = cor[idx]; |
255 | else if (cor[0] != 0xff) |
256 | priv->clkout = cor[0]; |
257 | else |
258 | priv->clkout = COR_DEFAULT; |
259 | |
260 | platform_set_drvdata(pdev, data: dev); |
261 | SET_NETDEV_DEV(dev, &pdev->dev); |
262 | |
263 | err = register_cc770dev(dev); |
264 | if (err) { |
265 | dev_err(&pdev->dev, |
266 | "couldn't register device (err=%d)\n" , err); |
267 | goto exit_free; |
268 | } |
269 | |
270 | dev_info(&pdev->dev, "device registered (reg_base=0x%p, irq=%d)\n" , |
271 | priv->reg_base, dev->irq); |
272 | return 0; |
273 | |
274 | exit_free: |
275 | free_cc770dev(dev); |
276 | exit_unmap: |
277 | if (mem[idx]) |
278 | iounmap(addr: base); |
279 | exit_release: |
280 | if (mem[idx]) |
281 | release_mem_region(mem[idx], iosize); |
282 | else |
283 | release_region(port[idx], iosize); |
284 | exit: |
285 | return err; |
286 | } |
287 | |
288 | static void cc770_isa_remove(struct platform_device *pdev) |
289 | { |
290 | struct net_device *dev = platform_get_drvdata(pdev); |
291 | struct cc770_priv *priv = netdev_priv(dev); |
292 | int idx = pdev->id; |
293 | |
294 | unregister_cc770dev(dev); |
295 | |
296 | if (mem[idx]) { |
297 | iounmap(addr: priv->reg_base); |
298 | release_mem_region(mem[idx], CC770_IOSIZE); |
299 | } else { |
300 | if (priv->read_reg == cc770_isa_port_read_reg_indirect) |
301 | release_region(port[idx], CC770_IOSIZE_INDIRECT); |
302 | else |
303 | release_region(port[idx], CC770_IOSIZE); |
304 | } |
305 | free_cc770dev(dev); |
306 | } |
307 | |
308 | static struct platform_driver cc770_isa_driver = { |
309 | .probe = cc770_isa_probe, |
310 | .remove_new = cc770_isa_remove, |
311 | .driver = { |
312 | .name = KBUILD_MODNAME, |
313 | }, |
314 | }; |
315 | |
316 | static int __init cc770_isa_init(void) |
317 | { |
318 | int idx, err; |
319 | |
320 | for (idx = 0; idx < ARRAY_SIZE(cc770_isa_devs); idx++) { |
321 | if ((port[idx] || mem[idx]) && irq[idx]) { |
322 | cc770_isa_devs[idx] = |
323 | platform_device_alloc(KBUILD_MODNAME, id: idx); |
324 | if (!cc770_isa_devs[idx]) { |
325 | err = -ENOMEM; |
326 | goto exit_free_devices; |
327 | } |
328 | err = platform_device_add(pdev: cc770_isa_devs[idx]); |
329 | if (err) { |
330 | platform_device_put(pdev: cc770_isa_devs[idx]); |
331 | goto exit_free_devices; |
332 | } |
333 | pr_debug("platform device %d: port=%#lx, mem=%#lx, " |
334 | "irq=%d\n" , |
335 | idx, port[idx], mem[idx], irq[idx]); |
336 | } else if (idx == 0 || port[idx] || mem[idx]) { |
337 | pr_err("insufficient parameters supplied\n" ); |
338 | err = -EINVAL; |
339 | goto exit_free_devices; |
340 | } |
341 | } |
342 | |
343 | err = platform_driver_register(&cc770_isa_driver); |
344 | if (err) |
345 | goto exit_free_devices; |
346 | |
347 | pr_info("driver for max. %d devices registered\n" , MAXDEV); |
348 | |
349 | return 0; |
350 | |
351 | exit_free_devices: |
352 | while (--idx >= 0) { |
353 | if (cc770_isa_devs[idx]) |
354 | platform_device_unregister(cc770_isa_devs[idx]); |
355 | } |
356 | |
357 | return err; |
358 | } |
359 | module_init(cc770_isa_init); |
360 | |
361 | static void __exit cc770_isa_exit(void) |
362 | { |
363 | int idx; |
364 | |
365 | platform_driver_unregister(&cc770_isa_driver); |
366 | for (idx = 0; idx < ARRAY_SIZE(cc770_isa_devs); idx++) { |
367 | if (cc770_isa_devs[idx]) |
368 | platform_device_unregister(cc770_isa_devs[idx]); |
369 | } |
370 | } |
371 | module_exit(cc770_isa_exit); |
372 | |