Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Platform CAN bus driver for Bosch C_CAN controller |
| 3 | * |
| 4 | * Copyright (C) 2010 ST Microelectronics |
| 5 | * Bhupesh Sharma <bhupesh.sharma@st.com> |
| 6 | * |
| 7 | * Borrowed heavily from the C_CAN driver originally written by: |
| 8 | * Copyright (C) 2007 |
| 9 | * - Sascha Hauer, Marc Kleine-Budde, Pengutronix <s.hauer@pengutronix.de> |
| 10 | * - Simon Kallweit, intefo AG <simon.kallweit@intefo.ch> |
| 11 | * |
| 12 | * Bosch C_CAN controller is compliant to CAN protocol version 2.0 part A and B. |
| 13 | * Bosch C_CAN user manual can be obtained from: |
| 14 | * http://www.semiconductors.bosch.de/media/en/pdf/ipmodules_1/c_can/ |
| 15 | * users_manual_c_can.pdf |
| 16 | * |
| 17 | * This file is licensed under the terms of the GNU General Public |
| 18 | * License version 2. This program is licensed "as is" without any |
| 19 | * warranty of any kind, whether express or implied. |
| 20 | */ |
| 21 | |
| 22 | #include <linux/kernel.h> |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 23 | #include <linux/module.h> |
| 24 | #include <linux/interrupt.h> |
| 25 | #include <linux/delay.h> |
| 26 | #include <linux/netdevice.h> |
| 27 | #include <linux/if_arp.h> |
| 28 | #include <linux/if_ether.h> |
| 29 | #include <linux/list.h> |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 30 | #include <linux/io.h> |
| 31 | #include <linux/platform_device.h> |
| 32 | #include <linux/clk.h> |
AnilKumar Ch | 2469627 | 2012-08-02 18:43:10 +0530 | [diff] [blame] | 33 | #include <linux/of.h> |
| 34 | #include <linux/of_device.h> |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 35 | |
| 36 | #include <linux/can/dev.h> |
| 37 | |
| 38 | #include "c_can.h" |
| 39 | |
| 40 | /* |
| 41 | * 16-bit c_can registers can be arranged differently in the memory |
| 42 | * architecture of different implementations. For example: 16-bit |
| 43 | * registers can be aligned to a 16-bit boundary or 32-bit boundary etc. |
| 44 | * Handle the same by providing a common read/write interface. |
| 45 | */ |
| 46 | static u16 c_can_plat_read_reg_aligned_to_16bit(struct c_can_priv *priv, |
AnilKumar Ch | 33f8100 | 2012-05-29 11:13:15 +0530 | [diff] [blame] | 47 | enum reg index) |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 48 | { |
AnilKumar Ch | 33f8100 | 2012-05-29 11:13:15 +0530 | [diff] [blame] | 49 | return readw(priv->base + priv->regs[index]); |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 50 | } |
| 51 | |
| 52 | static void c_can_plat_write_reg_aligned_to_16bit(struct c_can_priv *priv, |
AnilKumar Ch | 33f8100 | 2012-05-29 11:13:15 +0530 | [diff] [blame] | 53 | enum reg index, u16 val) |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 54 | { |
AnilKumar Ch | 33f8100 | 2012-05-29 11:13:15 +0530 | [diff] [blame] | 55 | writew(val, priv->base + priv->regs[index]); |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 56 | } |
| 57 | |
| 58 | static u16 c_can_plat_read_reg_aligned_to_32bit(struct c_can_priv *priv, |
AnilKumar Ch | 33f8100 | 2012-05-29 11:13:15 +0530 | [diff] [blame] | 59 | enum reg index) |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 60 | { |
AnilKumar Ch | 33f8100 | 2012-05-29 11:13:15 +0530 | [diff] [blame] | 61 | return readw(priv->base + 2 * priv->regs[index]); |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | static void c_can_plat_write_reg_aligned_to_32bit(struct c_can_priv *priv, |
AnilKumar Ch | 33f8100 | 2012-05-29 11:13:15 +0530 | [diff] [blame] | 65 | enum reg index, u16 val) |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 66 | { |
AnilKumar Ch | 33f8100 | 2012-05-29 11:13:15 +0530 | [diff] [blame] | 67 | writew(val, priv->base + 2 * priv->regs[index]); |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 68 | } |
| 69 | |
AnilKumar Ch | 2469627 | 2012-08-02 18:43:10 +0530 | [diff] [blame] | 70 | static struct platform_device_id c_can_id_table[] = { |
| 71 | [BOSCH_C_CAN_PLATFORM] = { |
| 72 | .name = KBUILD_MODNAME, |
| 73 | .driver_data = BOSCH_C_CAN, |
| 74 | }, |
| 75 | [BOSCH_C_CAN] = { |
| 76 | .name = "c_can", |
| 77 | .driver_data = BOSCH_C_CAN, |
| 78 | }, |
| 79 | [BOSCH_D_CAN] = { |
| 80 | .name = "d_can", |
| 81 | .driver_data = BOSCH_D_CAN, |
| 82 | }, { |
| 83 | } |
| 84 | }; |
| 85 | |
| 86 | static const struct of_device_id c_can_of_table[] = { |
| 87 | { .compatible = "bosch,c_can", .data = &c_can_id_table[BOSCH_C_CAN] }, |
| 88 | { .compatible = "bosch,d_can", .data = &c_can_id_table[BOSCH_D_CAN] }, |
| 89 | { /* sentinel */ }, |
| 90 | }; |
| 91 | |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 92 | static int __devinit c_can_plat_probe(struct platform_device *pdev) |
| 93 | { |
| 94 | int ret; |
| 95 | void __iomem *addr; |
| 96 | struct net_device *dev; |
| 97 | struct c_can_priv *priv; |
AnilKumar Ch | 2469627 | 2012-08-02 18:43:10 +0530 | [diff] [blame] | 98 | const struct of_device_id *match; |
AnilKumar Ch | 69927fc | 2012-05-29 11:13:16 +0530 | [diff] [blame] | 99 | const struct platform_device_id *id; |
Marc Kleine-Budde | b0052b0 | 2011-03-24 02:34:33 +0000 | [diff] [blame] | 100 | struct resource *mem; |
| 101 | int irq; |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 102 | struct clk *clk; |
| 103 | |
AnilKumar Ch | 2469627 | 2012-08-02 18:43:10 +0530 | [diff] [blame] | 104 | if (pdev->dev.of_node) { |
| 105 | match = of_match_device(c_can_of_table, &pdev->dev); |
| 106 | if (!match) { |
| 107 | dev_err(&pdev->dev, "Failed to find matching dt id\n"); |
| 108 | ret = -EINVAL; |
| 109 | goto exit; |
| 110 | } |
| 111 | id = match->data; |
| 112 | } else { |
| 113 | id = platform_get_device_id(pdev); |
| 114 | } |
| 115 | |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 116 | /* get the appropriate clk */ |
| 117 | clk = clk_get(&pdev->dev, NULL); |
| 118 | if (IS_ERR(clk)) { |
| 119 | dev_err(&pdev->dev, "no clock defined\n"); |
| 120 | ret = -ENODEV; |
| 121 | goto exit; |
| 122 | } |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 123 | |
| 124 | /* get the platform data */ |
| 125 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
Marc Kleine-Budde | b0052b0 | 2011-03-24 02:34:33 +0000 | [diff] [blame] | 126 | irq = platform_get_irq(pdev, 0); |
| 127 | if (!mem || irq <= 0) { |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 128 | ret = -ENODEV; |
| 129 | goto exit_free_clk; |
| 130 | } |
| 131 | |
| 132 | if (!request_mem_region(mem->start, resource_size(mem), |
| 133 | KBUILD_MODNAME)) { |
| 134 | dev_err(&pdev->dev, "resource unavailable\n"); |
| 135 | ret = -ENODEV; |
| 136 | goto exit_free_clk; |
| 137 | } |
| 138 | |
| 139 | addr = ioremap(mem->start, resource_size(mem)); |
| 140 | if (!addr) { |
| 141 | dev_err(&pdev->dev, "failed to map can port\n"); |
| 142 | ret = -ENOMEM; |
| 143 | goto exit_release_mem; |
| 144 | } |
| 145 | |
| 146 | /* allocate the c_can device */ |
| 147 | dev = alloc_c_can_dev(); |
| 148 | if (!dev) { |
| 149 | ret = -ENOMEM; |
| 150 | goto exit_iounmap; |
| 151 | } |
| 152 | |
| 153 | priv = netdev_priv(dev); |
AnilKumar Ch | 69927fc | 2012-05-29 11:13:16 +0530 | [diff] [blame] | 154 | switch (id->driver_data) { |
AnilKumar Ch | f27b1db | 2012-08-02 18:43:09 +0530 | [diff] [blame] | 155 | case BOSCH_C_CAN: |
AnilKumar Ch | 69927fc | 2012-05-29 11:13:16 +0530 | [diff] [blame] | 156 | priv->regs = reg_map_c_can; |
| 157 | switch (mem->flags & IORESOURCE_MEM_TYPE_MASK) { |
| 158 | case IORESOURCE_MEM_32BIT: |
| 159 | priv->read_reg = c_can_plat_read_reg_aligned_to_32bit; |
| 160 | priv->write_reg = c_can_plat_write_reg_aligned_to_32bit; |
| 161 | break; |
| 162 | case IORESOURCE_MEM_16BIT: |
| 163 | default: |
| 164 | priv->read_reg = c_can_plat_read_reg_aligned_to_16bit; |
| 165 | priv->write_reg = c_can_plat_write_reg_aligned_to_16bit; |
| 166 | break; |
| 167 | } |
| 168 | break; |
AnilKumar Ch | f27b1db | 2012-08-02 18:43:09 +0530 | [diff] [blame] | 169 | case BOSCH_D_CAN: |
AnilKumar Ch | 69927fc | 2012-05-29 11:13:16 +0530 | [diff] [blame] | 170 | priv->regs = reg_map_d_can; |
| 171 | priv->can.ctrlmode_supported |= CAN_CTRLMODE_3_SAMPLES; |
| 172 | priv->read_reg = c_can_plat_read_reg_aligned_to_16bit; |
| 173 | priv->write_reg = c_can_plat_write_reg_aligned_to_16bit; |
| 174 | break; |
| 175 | default: |
| 176 | ret = -EINVAL; |
| 177 | goto exit_free_device; |
| 178 | } |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 179 | |
Marc Kleine-Budde | b0052b0 | 2011-03-24 02:34:33 +0000 | [diff] [blame] | 180 | dev->irq = irq; |
AnilKumar Ch | 33f8100 | 2012-05-29 11:13:15 +0530 | [diff] [blame] | 181 | priv->base = addr; |
AnilKumar Ch | 4cdd34b | 2012-08-20 16:50:54 +0530 | [diff] [blame^] | 182 | priv->device = &pdev->dev; |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 183 | priv->can.clock.freq = clk_get_rate(clk); |
| 184 | priv->priv = clk; |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 185 | |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 186 | platform_set_drvdata(pdev, dev); |
| 187 | SET_NETDEV_DEV(dev, &pdev->dev); |
| 188 | |
| 189 | ret = register_c_can_dev(dev); |
| 190 | if (ret) { |
| 191 | dev_err(&pdev->dev, "registering %s failed (err=%d)\n", |
| 192 | KBUILD_MODNAME, ret); |
| 193 | goto exit_free_device; |
| 194 | } |
| 195 | |
| 196 | dev_info(&pdev->dev, "%s device registered (regs=%p, irq=%d)\n", |
AnilKumar Ch | 33f8100 | 2012-05-29 11:13:15 +0530 | [diff] [blame] | 197 | KBUILD_MODNAME, priv->base, dev->irq); |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 198 | return 0; |
| 199 | |
| 200 | exit_free_device: |
| 201 | platform_set_drvdata(pdev, NULL); |
| 202 | free_c_can_dev(dev); |
| 203 | exit_iounmap: |
| 204 | iounmap(addr); |
| 205 | exit_release_mem: |
| 206 | release_mem_region(mem->start, resource_size(mem)); |
| 207 | exit_free_clk: |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 208 | clk_put(clk); |
| 209 | exit: |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 210 | dev_err(&pdev->dev, "probe failed\n"); |
| 211 | |
| 212 | return ret; |
| 213 | } |
| 214 | |
| 215 | static int __devexit c_can_plat_remove(struct platform_device *pdev) |
| 216 | { |
| 217 | struct net_device *dev = platform_get_drvdata(pdev); |
| 218 | struct c_can_priv *priv = netdev_priv(dev); |
| 219 | struct resource *mem; |
| 220 | |
| 221 | unregister_c_can_dev(dev); |
| 222 | platform_set_drvdata(pdev, NULL); |
| 223 | |
| 224 | free_c_can_dev(dev); |
AnilKumar Ch | 33f8100 | 2012-05-29 11:13:15 +0530 | [diff] [blame] | 225 | iounmap(priv->base); |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 226 | |
| 227 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| 228 | release_mem_region(mem->start, resource_size(mem)); |
| 229 | |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 230 | clk_put(priv->priv); |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 231 | |
| 232 | return 0; |
| 233 | } |
| 234 | |
| 235 | static struct platform_driver c_can_plat_driver = { |
| 236 | .driver = { |
| 237 | .name = KBUILD_MODNAME, |
| 238 | .owner = THIS_MODULE, |
AnilKumar Ch | 2469627 | 2012-08-02 18:43:10 +0530 | [diff] [blame] | 239 | .of_match_table = of_match_ptr(c_can_of_table), |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 240 | }, |
| 241 | .probe = c_can_plat_probe, |
| 242 | .remove = __devexit_p(c_can_plat_remove), |
AnilKumar Ch | 69927fc | 2012-05-29 11:13:16 +0530 | [diff] [blame] | 243 | .id_table = c_can_id_table, |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 244 | }; |
| 245 | |
Axel Lin | 871d337 | 2011-11-27 15:42:31 +0000 | [diff] [blame] | 246 | module_platform_driver(c_can_plat_driver); |
Bhupesh Sharma | 881ff67 | 2011-02-13 22:51:44 -0800 | [diff] [blame] | 247 | |
| 248 | MODULE_AUTHOR("Bhupesh Sharma <bhupesh.sharma@st.com>"); |
| 249 | MODULE_LICENSE("GPL v2"); |
| 250 | MODULE_DESCRIPTION("Platform CAN bus driver for Bosch C_CAN controller"); |