Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Driver for CC770 and AN82527 CAN controllers on the legacy ISA bus
0004  *
0005  * Copyright (C) 2009, 2011 Wolfgang Grandegger <wg@grandegger.com>
0006  */
0007 
0008 /*
0009  * Bosch CC770 and Intel AN82527 CAN controllers on the ISA or PC-104 bus.
0010  * The I/O port or memory address and the IRQ number must be specified via
0011  * module parameters:
0012  *
0013  *   insmod cc770_isa.ko port=0x310,0x380 irq=7,11
0014  *
0015  * for ISA devices using I/O ports or:
0016  *
0017  *   insmod cc770_isa.ko mem=0xd1000,0xd1000 irq=7,11
0018  *
0019  * for memory mapped ISA devices.
0020  *
0021  * Indirect access via address and data port is supported as well:
0022  *
0023  *   insmod cc770_isa.ko port=0x310,0x380 indirect=1 irq=7,11
0024  *
0025  * Furthermore, the following mode parameter can be defined:
0026  *
0027  *   clk: External oscillator clock frequency (default=16000000 [16 MHz])
0028  *   cir: CPU interface register (default=0x40 [DSC])
0029  *   bcr: Bus configuration register (default=0x40 [CBY])
0030  *   cor: Clockout register (default=0x00)
0031  *
0032  * Note: for clk, cir, bcr and cor, the first argument re-defines the
0033  * default for all other devices, e.g.:
0034  *
0035  *   insmod cc770_isa.ko mem=0xd1000,0xd1000 irq=7,11 clk=24000000
0036  *
0037  * is equivalent to
0038  *
0039  *   insmod cc770_isa.ko mem=0xd1000,0xd1000 irq=7,11 clk=24000000,24000000
0040  */
0041 
0042 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0043 
0044 #include <linux/kernel.h>
0045 #include <linux/module.h>
0046 #include <linux/platform_device.h>
0047 #include <linux/interrupt.h>
0048 #include <linux/netdevice.h>
0049 #include <linux/delay.h>
0050 #include <linux/irq.h>
0051 #include <linux/io.h>
0052 #include <linux/can.h>
0053 #include <linux/can/dev.h>
0054 #include <linux/can/platform/cc770.h>
0055 
0056 #include "cc770.h"
0057 
0058 #define MAXDEV 8
0059 
0060 MODULE_AUTHOR("Wolfgang Grandegger <wg@grandegger.com>");
0061 MODULE_DESCRIPTION("Socket-CAN driver for CC770 on the ISA bus");
0062 MODULE_LICENSE("GPL v2");
0063 
0064 #define CLK_DEFAULT 16000000    /* 16 MHz */
0065 #define COR_DEFAULT 0x00
0066 #define BCR_DEFAULT BUSCFG_CBY
0067 
0068 static unsigned long port[MAXDEV];
0069 static unsigned long mem[MAXDEV];
0070 static int irq[MAXDEV];
0071 static int clk[MAXDEV];
0072 static u8 cir[MAXDEV] = {[0 ... (MAXDEV - 1)] = 0xff};
0073 static u8 cor[MAXDEV] = {[0 ... (MAXDEV - 1)] = 0xff};
0074 static u8 bcr[MAXDEV] = {[0 ... (MAXDEV - 1)] = 0xff};
0075 static int indirect[MAXDEV] = {[0 ... (MAXDEV - 1)] = -1};
0076 
0077 module_param_hw_array(port, ulong, ioport, NULL, 0444);
0078 MODULE_PARM_DESC(port, "I/O port number");
0079 
0080 module_param_hw_array(mem, ulong, iomem, NULL, 0444);
0081 MODULE_PARM_DESC(mem, "I/O memory address");
0082 
0083 module_param_hw_array(indirect, int, ioport, NULL, 0444);
0084 MODULE_PARM_DESC(indirect, "Indirect access via address and data port");
0085 
0086 module_param_hw_array(irq, int, irq, NULL, 0444);
0087 MODULE_PARM_DESC(irq, "IRQ number");
0088 
0089 module_param_array(clk, int, NULL, 0444);
0090 MODULE_PARM_DESC(clk, "External oscillator clock frequency "
0091          "(default=16000000 [16 MHz])");
0092 
0093 module_param_array(cir, byte, NULL, 0444);
0094 MODULE_PARM_DESC(cir, "CPU interface register (default=0x40 [DSC])");
0095 
0096 module_param_array(cor, byte, NULL, 0444);
0097 MODULE_PARM_DESC(cor, "Clockout register (default=0x00)");
0098 
0099 module_param_array(bcr, byte, NULL, 0444);
0100 MODULE_PARM_DESC(bcr, "Bus configuration register (default=0x40 [CBY])");
0101 
0102 #define CC770_IOSIZE          0x20
0103 #define CC770_IOSIZE_INDIRECT 0x02
0104 
0105 /* Spinlock for cc770_isa_port_write_reg_indirect
0106  * and cc770_isa_port_read_reg_indirect
0107  */
0108 static DEFINE_SPINLOCK(cc770_isa_port_lock);
0109 
0110 static struct platform_device *cc770_isa_devs[MAXDEV];
0111 
0112 static u8 cc770_isa_mem_read_reg(const struct cc770_priv *priv, int reg)
0113 {
0114     return readb(priv->reg_base + reg);
0115 }
0116 
0117 static void cc770_isa_mem_write_reg(const struct cc770_priv *priv,
0118                       int reg, u8 val)
0119 {
0120     writeb(val, priv->reg_base + reg);
0121 }
0122 
0123 static u8 cc770_isa_port_read_reg(const struct cc770_priv *priv, int reg)
0124 {
0125     return inb((unsigned long)priv->reg_base + reg);
0126 }
0127 
0128 static void cc770_isa_port_write_reg(const struct cc770_priv *priv,
0129                        int reg, u8 val)
0130 {
0131     outb(val, (unsigned long)priv->reg_base + reg);
0132 }
0133 
0134 static u8 cc770_isa_port_read_reg_indirect(const struct cc770_priv *priv,
0135                          int reg)
0136 {
0137     unsigned long base = (unsigned long)priv->reg_base;
0138     unsigned long flags;
0139     u8 val;
0140 
0141     spin_lock_irqsave(&cc770_isa_port_lock, flags);
0142     outb(reg, base);
0143     val = inb(base + 1);
0144     spin_unlock_irqrestore(&cc770_isa_port_lock, flags);
0145 
0146     return val;
0147 }
0148 
0149 static void cc770_isa_port_write_reg_indirect(const struct cc770_priv *priv,
0150                         int reg, u8 val)
0151 {
0152     unsigned long base = (unsigned long)priv->reg_base;
0153     unsigned long flags;
0154 
0155     spin_lock_irqsave(&cc770_isa_port_lock, flags);
0156     outb(reg, base);
0157     outb(val, base + 1);
0158     spin_unlock_irqrestore(&cc770_isa_port_lock, flags);
0159 }
0160 
0161 static int cc770_isa_probe(struct platform_device *pdev)
0162 {
0163     struct net_device *dev;
0164     struct cc770_priv *priv;
0165     void __iomem *base = NULL;
0166     int iosize = CC770_IOSIZE;
0167     int idx = pdev->id;
0168     int err;
0169     u32 clktmp;
0170 
0171     dev_dbg(&pdev->dev, "probing idx=%d: port=%#lx, mem=%#lx, irq=%d\n",
0172         idx, port[idx], mem[idx], irq[idx]);
0173     if (mem[idx]) {
0174         if (!request_mem_region(mem[idx], iosize, KBUILD_MODNAME)) {
0175             err = -EBUSY;
0176             goto exit;
0177         }
0178         base = ioremap(mem[idx], iosize);
0179         if (!base) {
0180             err = -ENOMEM;
0181             goto exit_release;
0182         }
0183     } else {
0184         if (indirect[idx] > 0 ||
0185             (indirect[idx] == -1 && indirect[0] > 0))
0186             iosize = CC770_IOSIZE_INDIRECT;
0187         if (!request_region(port[idx], iosize, KBUILD_MODNAME)) {
0188             err = -EBUSY;
0189             goto exit;
0190         }
0191     }
0192 
0193     dev = alloc_cc770dev(0);
0194     if (!dev) {
0195         err = -ENOMEM;
0196         goto exit_unmap;
0197     }
0198     priv = netdev_priv(dev);
0199 
0200     dev->irq = irq[idx];
0201     priv->irq_flags = IRQF_SHARED;
0202     if (mem[idx]) {
0203         priv->reg_base = base;
0204         dev->base_addr = mem[idx];
0205         priv->read_reg = cc770_isa_mem_read_reg;
0206         priv->write_reg = cc770_isa_mem_write_reg;
0207     } else {
0208         priv->reg_base = (void __iomem *)port[idx];
0209         dev->base_addr = port[idx];
0210 
0211         if (iosize == CC770_IOSIZE_INDIRECT) {
0212             priv->read_reg = cc770_isa_port_read_reg_indirect;
0213             priv->write_reg = cc770_isa_port_write_reg_indirect;
0214         } else {
0215             priv->read_reg = cc770_isa_port_read_reg;
0216             priv->write_reg = cc770_isa_port_write_reg;
0217         }
0218     }
0219 
0220     if (clk[idx])
0221         clktmp = clk[idx];
0222     else if (clk[0])
0223         clktmp = clk[0];
0224     else
0225         clktmp = CLK_DEFAULT;
0226     priv->can.clock.freq = clktmp;
0227 
0228     if (cir[idx] != 0xff) {
0229         priv->cpu_interface = cir[idx];
0230     } else if (cir[0] != 0xff) {
0231         priv->cpu_interface = cir[0];
0232     } else {
0233         /* The system clock may not exceed 10 MHz */
0234         if (clktmp > 10000000) {
0235             priv->cpu_interface |= CPUIF_DSC;
0236             clktmp /= 2;
0237         }
0238         /* The memory clock may not exceed 8 MHz */
0239         if (clktmp > 8000000)
0240             priv->cpu_interface |= CPUIF_DMC;
0241     }
0242 
0243     if (priv->cpu_interface & CPUIF_DSC)
0244         priv->can.clock.freq /= 2;
0245 
0246     if (bcr[idx] != 0xff)
0247         priv->bus_config = bcr[idx];
0248     else if (bcr[0] != 0xff)
0249         priv->bus_config = bcr[0];
0250     else
0251         priv->bus_config = BCR_DEFAULT;
0252 
0253     if (cor[idx] != 0xff)
0254         priv->clkout = cor[idx];
0255     else if (cor[0] != 0xff)
0256         priv->clkout = cor[0];
0257     else
0258         priv->clkout = COR_DEFAULT;
0259 
0260     platform_set_drvdata(pdev, dev);
0261     SET_NETDEV_DEV(dev, &pdev->dev);
0262 
0263     err = register_cc770dev(dev);
0264     if (err) {
0265         dev_err(&pdev->dev,
0266             "couldn't register device (err=%d)\n", err);
0267         goto exit_unmap;
0268     }
0269 
0270     dev_info(&pdev->dev, "device registered (reg_base=0x%p, irq=%d)\n",
0271          priv->reg_base, dev->irq);
0272     return 0;
0273 
0274  exit_unmap:
0275     if (mem[idx])
0276         iounmap(base);
0277  exit_release:
0278     if (mem[idx])
0279         release_mem_region(mem[idx], iosize);
0280     else
0281         release_region(port[idx], iosize);
0282  exit:
0283     return err;
0284 }
0285 
0286 static int cc770_isa_remove(struct platform_device *pdev)
0287 {
0288     struct net_device *dev = platform_get_drvdata(pdev);
0289     struct cc770_priv *priv = netdev_priv(dev);
0290     int idx = pdev->id;
0291 
0292     unregister_cc770dev(dev);
0293 
0294     if (mem[idx]) {
0295         iounmap(priv->reg_base);
0296         release_mem_region(mem[idx], CC770_IOSIZE);
0297     } else {
0298         if (priv->read_reg == cc770_isa_port_read_reg_indirect)
0299             release_region(port[idx], CC770_IOSIZE_INDIRECT);
0300         else
0301             release_region(port[idx], CC770_IOSIZE);
0302     }
0303     free_cc770dev(dev);
0304 
0305     return 0;
0306 }
0307 
0308 static struct platform_driver cc770_isa_driver = {
0309     .probe = cc770_isa_probe,
0310     .remove = cc770_isa_remove,
0311     .driver = {
0312         .name = KBUILD_MODNAME,
0313     },
0314 };
0315 
0316 static int __init cc770_isa_init(void)
0317 {
0318     int idx, err;
0319 
0320     for (idx = 0; idx < ARRAY_SIZE(cc770_isa_devs); idx++) {
0321         if ((port[idx] || mem[idx]) && irq[idx]) {
0322             cc770_isa_devs[idx] =
0323                 platform_device_alloc(KBUILD_MODNAME, idx);
0324             if (!cc770_isa_devs[idx]) {
0325                 err = -ENOMEM;
0326                 goto exit_free_devices;
0327             }
0328             err = platform_device_add(cc770_isa_devs[idx]);
0329             if (err) {
0330                 platform_device_put(cc770_isa_devs[idx]);
0331                 goto exit_free_devices;
0332             }
0333             pr_debug("platform device %d: port=%#lx, mem=%#lx, "
0334                  "irq=%d\n",
0335                  idx, port[idx], mem[idx], irq[idx]);
0336         } else if (idx == 0 || port[idx] || mem[idx]) {
0337             pr_err("insufficient parameters supplied\n");
0338             err = -EINVAL;
0339             goto exit_free_devices;
0340         }
0341     }
0342 
0343     err = platform_driver_register(&cc770_isa_driver);
0344     if (err)
0345         goto exit_free_devices;
0346 
0347     pr_info("driver for max. %d devices registered\n", MAXDEV);
0348 
0349     return 0;
0350 
0351 exit_free_devices:
0352     while (--idx >= 0) {
0353         if (cc770_isa_devs[idx])
0354             platform_device_unregister(cc770_isa_devs[idx]);
0355     }
0356 
0357     return err;
0358 }
0359 module_init(cc770_isa_init);
0360 
0361 static void __exit cc770_isa_exit(void)
0362 {
0363     int idx;
0364 
0365     platform_driver_unregister(&cc770_isa_driver);
0366     for (idx = 0; idx < ARRAY_SIZE(cc770_isa_devs); idx++) {
0367         if (cc770_isa_devs[idx])
0368             platform_device_unregister(cc770_isa_devs[idx]);
0369     }
0370 }
0371 module_exit(cc770_isa_exit);