// SPDX-License-Identifier: GPL-2.0-only /* * Driver for CC770 and AN82527 CAN controllers on the platform bus * * Copyright (C) 2009, 2011 Wolfgang Grandegger */ /* * If platform data are used you should have similar definitions * in your board-specific code: * * static struct cc770_platform_data myboard_cc770_pdata = { * .osc_freq = 16000000, * .cir = 0x41, * .cor = 0x20, * .bcr = 0x40, * }; * * Please see include/linux/can/platform/cc770.h for description of * above fields. * * If the device tree is used, you need a CAN node definition in your * DTS file similar to: * * can@3,100 { * compatible = "bosch,cc770"; * reg = <3 0x100 0x80>; * interrupts = <2 0>; * interrupt-parent = <&mpic>; * bosch,external-clock-frequency = <16000000>; * }; * * See "Documentation/devicetree/bindings/net/can/cc770.txt" for further * information. */ #include #include #include #include #include #include #include #include #include #include #include "cc770.h" #define DRV_NAME "cc770_platform" MODULE_AUTHOR("Wolfgang Grandegger "); MODULE_DESCRIPTION("Socket-CAN driver for CC770 on the platform bus"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:" DRV_NAME); #define CC770_PLATFORM_CAN_CLOCK 16000000 static u8 cc770_platform_read_reg(const struct cc770_priv *priv, int reg) { return ioread8(priv->reg_base + reg); } static void cc770_platform_write_reg(const struct cc770_priv *priv, int reg, u8 val) { iowrite8(val, priv->reg_base + reg); } static int cc770_get_of_node_data(struct platform_device *pdev, struct cc770_priv *priv) { struct device_node *np = pdev->dev.of_node; const u32 *prop; int prop_size; u32 clkext; prop = of_get_property(np, "bosch,external-clock-frequency", &prop_size); if (prop && (prop_size == sizeof(u32))) clkext = *prop; else clkext = CC770_PLATFORM_CAN_CLOCK; /* default */ priv->can.clock.freq = clkext; /* The system clock may not exceed 10 MHz */ if (priv->can.clock.freq > 10000000) { priv->cpu_interface |= CPUIF_DSC; priv->can.clock.freq /= 2; } /* The memory clock may not exceed 8 MHz */ if (priv->can.clock.freq > 8000000) priv->cpu_interface |= CPUIF_DMC; if (of_get_property(np, "bosch,divide-memory-clock", NULL)) priv->cpu_interface |= CPUIF_DMC; if (of_get_property(np, "bosch,iso-low-speed-mux", NULL)) priv->cpu_interface |= CPUIF_MUX; if (!of_get_property(np, "bosch,no-comperator-bypass", NULL)) priv->bus_config |= BUSCFG_CBY; if (of_get_property(np, "bosch,disconnect-rx0-input", NULL)) priv->bus_config |= BUSCFG_DR0; if (of_get_property(np, "bosch,disconnect-rx1-input", NULL)) priv->bus_config |= BUSCFG_DR1; if (of_get_property(np, "bosch,disconnect-tx1-output", NULL)) priv->bus_config |= BUSCFG_DT1; if (of_get_property(np, "bosch,polarity-dominant", NULL)) priv->bus_config |= BUSCFG_POL; prop = of_get_property(np, "bosch,clock-out-frequency", &prop_size); if (prop && (prop_size == sizeof(u32)) && *prop > 0) { u32 cdv = clkext / *prop; int slew; if (cdv > 0 && cdv < 16) { priv->cpu_interface |= CPUIF_CEN; priv->clkout |= (cdv - 1) & CLKOUT_CD_MASK; prop = of_get_property(np, "bosch,slew-rate", &prop_size); if (prop && (prop_size == sizeof(u32))) { slew = *prop; } else { /* Determine default slew rate */ slew = (CLKOUT_SL_MASK >> CLKOUT_SL_SHIFT) - ((cdv * clkext - 1) / 8000000); if (slew < 0) slew = 0; } priv->clkout |= (slew << CLKOUT_SL_SHIFT) & CLKOUT_SL_MASK; } else { dev_dbg(&pdev->dev, "invalid clock-out-frequency\n"); } } return 0; } static int cc770_get_platform_data(struct platform_device *pdev, struct cc770_priv *priv) { struct cc770_platform_data *pdata = dev_get_platdata(&pdev->dev); priv->can.clock.freq = pdata->osc_freq; if (priv->cpu_interface & CPUIF_DSC) priv->can.clock.freq /= 2; priv->clkout = pdata->cor; priv->bus_config = pdata->bcr; priv->cpu_interface = pdata->cir; return 0; } static int cc770_platform_probe(struct platform_device *pdev) { struct net_device *dev; struct cc770_priv *priv; struct resource *mem; resource_size_t mem_size; void __iomem *base; int err, irq; mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); if (!mem || irq <= 0) return -ENODEV; mem_size = resource_size(mem); if (!request_mem_region(mem->start, mem_size, pdev->name)) return -EBUSY; base = ioremap(mem->start, mem_size); if (!base) { err = -ENOMEM; goto exit_release_mem; } dev = alloc_cc770dev(0); if (!dev) { err = -ENOMEM; goto exit_unmap_mem; } dev->irq = irq; priv = netdev_priv(dev); priv->read_reg = cc770_platform_read_reg; priv->write_reg = cc770_platform_write_reg; priv->irq_flags = IRQF_SHARED; priv->reg_base = base; if (pdev->dev.of_node) err = cc770_get_of_node_data(pdev, priv); else if (dev_get_platdata(&pdev->dev)) err = cc770_get_platform_data(pdev, priv); else err = -ENODEV; if (err) goto exit_free_cc770; dev_dbg(&pdev->dev, "reg_base=0x%p irq=%d clock=%d cpu_interface=0x%02x " "bus_config=0x%02x clkout=0x%02x\n", priv->reg_base, dev->irq, priv->can.clock.freq, priv->cpu_interface, priv->bus_config, priv->clkout); platform_set_drvdata(pdev, dev); SET_NETDEV_DEV(dev, &pdev->dev); err = register_cc770dev(dev); if (err) { dev_err(&pdev->dev, "couldn't register CC700 device (err=%d)\n", err); goto exit_free_cc770; } return 0; exit_free_cc770: free_cc770dev(dev); exit_unmap_mem: iounmap(base); exit_release_mem: release_mem_region(mem->start, mem_size); return err; } static int cc770_platform_remove(struct platform_device *pdev) { struct net_device *dev = platform_get_drvdata(pdev); struct cc770_priv *priv = netdev_priv(dev); struct resource *mem; unregister_cc770dev(dev); iounmap(priv->reg_base); free_cc770dev(dev); mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); release_mem_region(mem->start, resource_size(mem)); return 0; } static const struct of_device_id cc770_platform_table[] = { {.compatible = "bosch,cc770"}, /* CC770 from Bosch */ {.compatible = "intc,82527"}, /* AN82527 from Intel CP */ {}, }; MODULE_DEVICE_TABLE(of, cc770_platform_table); static struct platform_driver cc770_platform_driver = { .driver = { .name = DRV_NAME, .of_match_table = cc770_platform_table, }, .probe = cc770_platform_probe, .remove = cc770_platform_remove, }; module_platform_driver(cc770_platform_driver);