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