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