/* * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. */ /* * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ /*! * @file dev.c * * @brief Driver for Freescale CAN Controller FlexCAN. * * @ingroup can */ #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_ARCH_MXS #include #endif #include "flexcan.h" #define DEFAULT_BITRATE 500000 #define TIME_SEGMENT_MIN 8 #define TIME_SEGMENT_MAX 25 #define TIME_SEGMENT_MID ((TIME_SEGMENT_MIN + TIME_SEGMENT_MAX)/2) struct time_segment { char propseg; char pseg1; char pseg2; }; struct time_segment time_segments[] = { { /* total 8 timequanta */ 1, 2, 1 }, { /* total 9 timequanta */ 1, 2, 2 }, { /* total 10 timequanta */ 2, 2, 2 }, { /* total 11 timequanta */ 2, 2, 3 }, { /* total 12 timequanta */ 2, 3, 3 }, { /* total 13 timequanta */ 3, 3, 3 }, { /* total 14 timequanta */ 3, 3, 4 }, { /* total 15 timequanta */ 3, 4, 4 }, { /* total 16 timequanta */ 4, 4, 4 }, { /* total 17 timequanta */ 4, 4, 5 }, { /* total 18 timequanta */ 4, 5, 5 }, { /* total 19 timequanta */ 5, 5, 5 }, { /* total 20 timequanta */ 5, 5, 6 }, { /* total 21 timequanta */ 5, 6, 6 }, { /* total 22 timequanta */ 6, 6, 6 }, { /* total 23 timequanta */ 6, 6, 7 }, { /* total 24 timequanta */ 6, 7, 7 }, { /* total 25 timequanta */ 7, 7, 7 }, }; enum { FLEXCAN_ATTR_STATE = 0, FLEXCAN_ATTR_BITRATE, FLEXCAN_ATTR_BR_PRESDIV, FLEXCAN_ATTR_BR_RJW, FLEXCAN_ATTR_BR_PROPSEG, FLEXCAN_ATTR_BR_PSEG1, FLEXCAN_ATTR_BR_PSEG2, FLEXCAN_ATTR_BR_CLKSRC, FLEXCAN_ATTR_MAXMB, FLEXCAN_ATTR_XMIT_MAXMB, FLEXCAN_ATTR_FIFO, FLEXCAN_ATTR_WAKEUP, FLEXCAN_ATTR_SRX_DIS, FLEXCAN_ATTR_WAK_SRC, FLEXCAN_ATTR_BCC, FLEXCAN_ATTR_LOCAL_PRIORITY, FLEXCAN_ATTR_ABORT, FLEXCAN_ATTR_LOOPBACK, FLEXCAN_ATTR_SMP, FLEXCAN_ATTR_BOFF_REC, FLEXCAN_ATTR_TSYN, FLEXCAN_ATTR_LISTEN, FLEXCAN_ATTR_EXTEND_MSG, FLEXCAN_ATTR_STANDARD_MSG, #ifdef CONFIG_CAN_DEBUG_DEVICES FLEXCAN_ATTR_DUMP_REG, FLEXCAN_ATTR_DUMP_XMIT_MB, FLEXCAN_ATTR_DUMP_RX_MB, #endif FLEXCAN_ATTR_MAX }; static ssize_t flexcan_show_attr(struct device *dev, struct device_attribute *attr, char *buf); static ssize_t flexcan_set_attr(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); static struct device_attribute flexcan_dev_attr[FLEXCAN_ATTR_MAX] = { [FLEXCAN_ATTR_STATE] = __ATTR(state, 0444, flexcan_show_attr, NULL), [FLEXCAN_ATTR_BITRATE] = __ATTR(bitrate, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_BR_PRESDIV] = __ATTR(br_presdiv, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_BR_RJW] = __ATTR(br_rjw, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_BR_PROPSEG] = __ATTR(br_propseg, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_BR_PSEG1] = __ATTR(br_pseg1, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_BR_PSEG2] = __ATTR(br_pseg2, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_BR_CLKSRC] = __ATTR(br_clksrc, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_MAXMB] = __ATTR(maxmb, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_XMIT_MAXMB] = __ATTR(xmit_maxmb, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_FIFO] = __ATTR(fifo, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_WAKEUP] = __ATTR(wakeup, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_SRX_DIS] = __ATTR(srx_dis, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_WAK_SRC] = __ATTR(wak_src, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_BCC] = __ATTR(bcc, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_LOCAL_PRIORITY] = __ATTR(local_priority, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_ABORT] = __ATTR(abort, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_LOOPBACK] = __ATTR(loopback, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_SMP] = __ATTR(smp, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_BOFF_REC] = __ATTR(boff_rec, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_TSYN] = __ATTR(tsyn, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_LISTEN] = __ATTR(listen, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_EXTEND_MSG] = __ATTR(ext_msg, 0644, flexcan_show_attr, flexcan_set_attr), [FLEXCAN_ATTR_STANDARD_MSG] = __ATTR(std_msg, 0644, flexcan_show_attr, flexcan_set_attr), #ifdef CONFIG_CAN_DEBUG_DEVICES [FLEXCAN_ATTR_DUMP_REG] = __ATTR(dump_reg, 0444, flexcan_show_attr, NULL), [FLEXCAN_ATTR_DUMP_XMIT_MB] = __ATTR(dump_xmit_mb, 0444, flexcan_show_attr, NULL), [FLEXCAN_ATTR_DUMP_RX_MB] = __ATTR(dump_rx_mb, 0444, flexcan_show_attr, NULL), #endif }; static void flexcan_set_bitrate(struct flexcan_device *flexcan, int bitrate) { /* TODO:: implement in future * based on the bitrate to get the timing of * presdiv, pseg1, pseg2, propseg */ int i, rate, div; bool found = false; struct time_segment *segment; rate = clk_get_rate(flexcan->clk); if (!bitrate) bitrate = DEFAULT_BITRATE; if (rate % bitrate == 0) { div = rate / bitrate; for (i = TIME_SEGMENT_MID; i <= TIME_SEGMENT_MAX; i++) { if (div % i == 0) { found = true; break; } } if (!found) { for (i = TIME_SEGMENT_MID - 1; i >= TIME_SEGMENT_MIN; i--) { if (div % i == 0) { found = true; break; } } } } if (found) { segment = &time_segments[i - TIME_SEGMENT_MIN]; flexcan->br_presdiv = div/i - 1; flexcan->br_propseg = segment->propseg; flexcan->br_pseg1 = segment->pseg1; flexcan->br_pseg2 = segment->pseg2; flexcan->bitrate = bitrate; } else { pr_info("The bitrate %d can't supported with clock \ rate of %d \n", bitrate, rate); } } static void flexcan_update_bitrate(struct flexcan_device *flexcan) { int rate, div; if (flexcan->br_clksrc) rate = clk_get_rate(flexcan->clk); else { struct clk *clk; clk = clk_get(NULL, "ckih"); if (!clk) return; rate = clk_get_rate(clk); clk_put(clk); } if (!rate) return; div = (flexcan->br_presdiv + 1); div *= (flexcan->br_propseg + flexcan->br_pseg1 + flexcan->br_pseg2 + 4); flexcan->bitrate = (rate + div - 1) / div; } #ifdef CONFIG_CAN_DEBUG_DEVICES static int flexcan_dump_reg(struct flexcan_device *flexcan, char *buf) { int ret = 0; unsigned int reg; reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); ret += sprintf(buf + ret, "MCR::0x%x\n", reg); reg = __raw_readl(flexcan->io_base + CAN_HW_REG_CTRL); ret += sprintf(buf + ret, "CTRL::0x%x\n", reg); reg = __raw_readl(flexcan->io_base + CAN_HW_REG_RXGMASK); ret += sprintf(buf + ret, "RXGMASK::0x%x\n", reg); reg = __raw_readl(flexcan->io_base + CAN_HW_REG_RX14MASK); ret += sprintf(buf + ret, "RX14MASK::0x%x\n", reg); reg = __raw_readl(flexcan->io_base + CAN_HW_REG_RX15MASK); ret += sprintf(buf + ret, "RX15MASK::0x%x\n", reg); reg = __raw_readl(flexcan->io_base + CAN_HW_REG_ECR); ret += sprintf(buf + ret, "ECR::0x%x\n", reg); reg = __raw_readl(flexcan->io_base + CAN_HW_REG_ESR); ret += sprintf(buf + ret, "ESR::0x%x\n", reg); reg = __raw_readl(flexcan->io_base + CAN_HW_REG_IMASK2); ret += sprintf(buf + ret, "IMASK2::0x%x\n", reg); reg = __raw_readl(flexcan->io_base + CAN_HW_REG_IMASK1); ret += sprintf(buf + ret, "IMASK1::0x%x\n", reg); reg = __raw_readl(flexcan->io_base + CAN_HW_REG_IFLAG2); ret += sprintf(buf + ret, "IFLAG2::0x%x\n", reg); reg = __raw_readl(flexcan->io_base + CAN_HW_REG_IFLAG1); ret += sprintf(buf + ret, "IFLAG1::0x%x\n", reg); return ret; } static int flexcan_dump_xmit_mb(struct flexcan_device *flexcan, char *buf) { int ret = 0, i; i = flexcan->xmit_maxmb + 1; for (; i <= flexcan->maxmb; i++) ret += sprintf(buf + ret, "mb[%d]::CS:0x%x ID:0x%x DATA[1~2]:0x%02x,0x%02x\n", i, flexcan->hwmb[i].mb_cs, flexcan->hwmb[i].mb_id, flexcan->hwmb[i].mb_data[1], flexcan->hwmb[i].mb_data[2]); return ret; } static int flexcan_dump_rx_mb(struct flexcan_device *flexcan, char *buf) { int ret = 0, i; for (i = 0; i <= flexcan->xmit_maxmb; i++) ret += sprintf(buf + ret, "mb[%d]::CS:0x%x ID:0x%x DATA[1~2]:0x%02x,0x%02x\n", i, flexcan->hwmb[i].mb_cs, flexcan->hwmb[i].mb_id, flexcan->hwmb[i].mb_data[1], flexcan->hwmb[i].mb_data[2]); return ret; } #endif static ssize_t flexcan_show_state(struct net_device *net, char *buf) { int ret, esr; struct flexcan_device *flexcan = netdev_priv(net); ret = sprintf(buf, "%s::", netif_running(net) ? "Start" : "Stop"); if (netif_carrier_ok(net)) { esr = __raw_readl(flexcan->io_base + CAN_HW_REG_ESR); switch ((esr & __ESR_FLT_CONF_MASK) >> __ESR_FLT_CONF_OFF) { case 0: ret += sprintf(buf + ret, "normal\n"); break; case 1: ret += sprintf(buf + ret, "error passive\n"); break; default: ret += sprintf(buf + ret, "bus off\n"); } } else ret += sprintf(buf + ret, "bus off\n"); return ret; } static ssize_t flexcan_show_attr(struct device *dev, struct device_attribute *attr, char *buf) { int attr_id; struct net_device *net; struct flexcan_device *flexcan; net = dev_get_drvdata(dev); BUG_ON(!net); flexcan = netdev_priv(net); BUG_ON(!flexcan); attr_id = attr - flexcan_dev_attr; switch (attr_id) { case FLEXCAN_ATTR_STATE: return flexcan_show_state(net, buf); case FLEXCAN_ATTR_BITRATE: return sprintf(buf, "%d\n", flexcan->bitrate); case FLEXCAN_ATTR_BR_PRESDIV: return sprintf(buf, "%d\n", flexcan->br_presdiv + 1); case FLEXCAN_ATTR_BR_RJW: return sprintf(buf, "%d\n", flexcan->br_rjw); case FLEXCAN_ATTR_BR_PROPSEG: return sprintf(buf, "%d\n", flexcan->br_propseg + 1); case FLEXCAN_ATTR_BR_PSEG1: return sprintf(buf, "%d\n", flexcan->br_pseg1 + 1); case FLEXCAN_ATTR_BR_PSEG2: return sprintf(buf, "%d\n", flexcan->br_pseg2 + 1); case FLEXCAN_ATTR_BR_CLKSRC: return sprintf(buf, "%s\n", flexcan->br_clksrc ? "bus" : "osc"); case FLEXCAN_ATTR_MAXMB: return sprintf(buf, "%d\n", flexcan->maxmb + 1); case FLEXCAN_ATTR_XMIT_MAXMB: return sprintf(buf, "%d\n", flexcan->xmit_maxmb + 1); case FLEXCAN_ATTR_FIFO: return sprintf(buf, "%d\n", flexcan->fifo); case FLEXCAN_ATTR_WAKEUP: return sprintf(buf, "%d\n", flexcan->wakeup); case FLEXCAN_ATTR_SRX_DIS: return sprintf(buf, "%d\n", flexcan->srx_dis); case FLEXCAN_ATTR_WAK_SRC: return sprintf(buf, "%d\n", flexcan->wak_src); case FLEXCAN_ATTR_BCC: return sprintf(buf, "%d\n", flexcan->bcc); case FLEXCAN_ATTR_LOCAL_PRIORITY: return sprintf(buf, "%d\n", flexcan->lprio); case FLEXCAN_ATTR_ABORT: return sprintf(buf, "%d\n", flexcan->abort); case FLEXCAN_ATTR_LOOPBACK: return sprintf(buf, "%d\n", flexcan->loopback); case FLEXCAN_ATTR_SMP: return sprintf(buf, "%d\n", flexcan->smp); case FLEXCAN_ATTR_BOFF_REC: return sprintf(buf, "%d\n", flexcan->boff_rec); case FLEXCAN_ATTR_TSYN: return sprintf(buf, "%d\n", flexcan->tsyn); case FLEXCAN_ATTR_LISTEN: return sprintf(buf, "%d\n", flexcan->listen); case FLEXCAN_ATTR_EXTEND_MSG: return sprintf(buf, "%d\n", flexcan->ext_msg); case FLEXCAN_ATTR_STANDARD_MSG: return sprintf(buf, "%d\n", flexcan->std_msg); #ifdef CONFIG_CAN_DEBUG_DEVICES case FLEXCAN_ATTR_DUMP_REG: return flexcan_dump_reg(flexcan, buf); case FLEXCAN_ATTR_DUMP_XMIT_MB: return flexcan_dump_xmit_mb(flexcan, buf); case FLEXCAN_ATTR_DUMP_RX_MB: return flexcan_dump_rx_mb(flexcan, buf); #endif default: return sprintf(buf, "%s:%p->%p\n", __func__, flexcan_dev_attr, attr); } } static ssize_t flexcan_set_attr(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int attr_id, tmp; struct net_device *net; struct flexcan_device *flexcan; net = dev_get_drvdata(dev); BUG_ON(!net); flexcan = netdev_priv(net); BUG_ON(!flexcan); attr_id = attr - flexcan_dev_attr; if (mutex_lock_interruptible(&flexcan->mutex)) return count; if (netif_running(net)) goto set_finish; if (attr_id == FLEXCAN_ATTR_BR_CLKSRC) { if (!strncasecmp(buf, "bus", 3)) flexcan->br_clksrc = 1; else if (!strncasecmp(buf, "osc", 3)) flexcan->br_clksrc = 0; goto set_finish; } tmp = simple_strtoul(buf, NULL, 0); switch (attr_id) { case FLEXCAN_ATTR_BITRATE: flexcan_set_bitrate(flexcan, tmp); break; case FLEXCAN_ATTR_BR_PRESDIV: if ((tmp > 0) && (tmp <= FLEXCAN_MAX_PRESDIV)) { flexcan->br_presdiv = tmp - 1; flexcan_update_bitrate(flexcan); } break; case FLEXCAN_ATTR_BR_RJW: if ((tmp > 0) && (tmp <= FLEXCAN_MAX_RJW)) flexcan->br_rjw = tmp - 1; break; case FLEXCAN_ATTR_BR_PROPSEG: if ((tmp > 0) && (tmp <= FLEXCAN_MAX_PROPSEG)) { flexcan->br_propseg = tmp - 1; flexcan_update_bitrate(flexcan); } break; case FLEXCAN_ATTR_BR_PSEG1: if ((tmp > 0) && (tmp <= FLEXCAN_MAX_PSEG1)) { flexcan->br_pseg1 = tmp - 1; flexcan_update_bitrate(flexcan); } break; case FLEXCAN_ATTR_BR_PSEG2: if ((tmp > 0) && (tmp <= FLEXCAN_MAX_PSEG2)) { flexcan->br_pseg2 = tmp - 1; flexcan_update_bitrate(flexcan); } break; case FLEXCAN_ATTR_MAXMB: if ((tmp > 0) && (tmp <= FLEXCAN_MAX_MB)) { if (flexcan->maxmb != (tmp - 1)) { flexcan->maxmb = tmp - 1; if (flexcan->xmit_maxmb < flexcan->maxmb) flexcan->xmit_maxmb = flexcan->maxmb; } } break; case FLEXCAN_ATTR_XMIT_MAXMB: if ((tmp > 0) && (tmp <= (flexcan->maxmb + 1))) { if (flexcan->xmit_maxmb != (tmp - 1)) flexcan->xmit_maxmb = tmp - 1; } break; case FLEXCAN_ATTR_FIFO: flexcan->fifo = tmp ? 1 : 0; break; case FLEXCAN_ATTR_WAKEUP: flexcan->wakeup = tmp ? 1 : 0; break; case FLEXCAN_ATTR_SRX_DIS: flexcan->srx_dis = tmp ? 1 : 0; break; case FLEXCAN_ATTR_WAK_SRC: flexcan->wak_src = tmp ? 1 : 0; break; case FLEXCAN_ATTR_BCC: flexcan->bcc = tmp ? 1 : 0; break; case FLEXCAN_ATTR_LOCAL_PRIORITY: flexcan->lprio = tmp ? 1 : 0; break; case FLEXCAN_ATTR_ABORT: flexcan->abort = tmp ? 1 : 0; break; case FLEXCAN_ATTR_LOOPBACK: flexcan->loopback = tmp ? 1 : 0; break; case FLEXCAN_ATTR_SMP: flexcan->smp = tmp ? 1 : 0; break; case FLEXCAN_ATTR_BOFF_REC: flexcan->boff_rec = tmp ? 1 : 0; break; case FLEXCAN_ATTR_TSYN: flexcan->tsyn = tmp ? 1 : 0; break; case FLEXCAN_ATTR_LISTEN: flexcan->listen = tmp ? 1 : 0; break; case FLEXCAN_ATTR_EXTEND_MSG: flexcan->ext_msg = tmp ? 1 : 0; break; case FLEXCAN_ATTR_STANDARD_MSG: flexcan->std_msg = tmp ? 1 : 0; break; } set_finish: mutex_unlock(&flexcan->mutex); return count; } static void flexcan_device_default(struct flexcan_device *dev) { struct platform_device *pdev = dev->dev; struct flexcan_platform_data *plat_data = (pdev->dev).platform_data; dev->br_clksrc = plat_data->br_clksrc; dev->br_rjw = plat_data->br_rjw; dev->br_presdiv = plat_data->br_presdiv; dev->br_propseg = plat_data->br_propseg; dev->br_pseg1 = plat_data->br_pseg1; dev->br_pseg2 = plat_data->br_pseg2; dev->bcc = plat_data->bcc; dev->srx_dis = plat_data->srx_dis; dev->smp = plat_data->smp; dev->boff_rec = plat_data->boff_rec; dev->maxmb = FLEXCAN_MAX_MB - 1; dev->xmit_maxmb = (FLEXCAN_MAX_MB >> 1) - 1; dev->xmit_mb = dev->maxmb - dev->xmit_maxmb; dev->ext_msg = plat_data->ext_msg; dev->std_msg = plat_data->std_msg; } static int flexcan_device_attach(struct flexcan_device *flexcan) { int ret; struct resource *res; struct platform_device *pdev = flexcan->dev; struct flexcan_platform_data *plat_data = (pdev->dev).platform_data; res = platform_get_resource(flexcan->dev, IORESOURCE_MEM, 0); if (!res) return -ENODEV; flexcan->io_base = ioremap(res->start, res->end - res->start + 1); if (!flexcan->io_base) return -ENOMEM; flexcan->irq = platform_get_irq(flexcan->dev, 0); if (!flexcan->irq) { ret = -ENODEV; goto no_irq_err; } ret = -EINVAL; if (plat_data) { if (plat_data->core_reg) { flexcan->core_reg = regulator_get(&pdev->dev, plat_data->core_reg); if (!flexcan->core_reg) goto plat_err; } if (plat_data->io_reg) { flexcan->io_reg = regulator_get(&pdev->dev, plat_data->io_reg); if (!flexcan->io_reg) goto plat_err; } } flexcan->clk = clk_get(&(flexcan->dev)->dev, "can_clk"); flexcan->hwmb = (struct can_hw_mb *)(flexcan->io_base + CAN_MB_BASE); flexcan->rx_mask = (unsigned int *)(flexcan->io_base + CAN_RXMASK_BASE); return 0; plat_err: if (flexcan->core_reg) { regulator_put(flexcan->core_reg); flexcan->core_reg = NULL; } no_irq_err: if (flexcan->io_base) iounmap(flexcan->io_base); return ret; } static void flexcan_device_detach(struct flexcan_device *flexcan) { if (flexcan->clk) { clk_put(flexcan->clk); flexcan->clk = NULL; } if (flexcan->io_reg) { regulator_put(flexcan->io_reg); flexcan->io_reg = NULL; } if (flexcan->core_reg) { regulator_put(flexcan->core_reg); flexcan->core_reg = NULL; } if (flexcan->io_base) iounmap(flexcan->io_base); } /*! * @brief The function allocates can device. * * @param pdev the pointer of platform device. * @param setup the initial function pointer of network device. * * @return none */ struct net_device *flexcan_device_alloc(struct platform_device *pdev, void (*setup) (struct net_device *dev)) { struct flexcan_device *flexcan; struct net_device *net; int i, num; net = alloc_netdev(sizeof(*flexcan), "can%d", setup); if (net == NULL) { printk(KERN_ERR "Allocate netdevice for FlexCAN fail!\n"); return NULL; } flexcan = netdev_priv(net); memset(flexcan, 0, sizeof(*flexcan)); mutex_init(&flexcan->mutex); init_timer(&flexcan->timer); flexcan->dev = pdev; if (flexcan_device_attach(flexcan)) { printk(KERN_ERR "Attach FlexCAN fail!\n"); free_netdev(net); return NULL; } flexcan_device_default(flexcan); flexcan_set_bitrate(flexcan, flexcan->bitrate); flexcan_update_bitrate(flexcan); num = ARRAY_SIZE(flexcan_dev_attr); for (i = 0; i < num; i++) { if (device_create_file(&pdev->dev, flexcan_dev_attr + i)) { printk(KERN_ERR "Create attribute file fail!\n"); break; } } if (i != num) { for (; i >= 0; i--) device_remove_file(&pdev->dev, flexcan_dev_attr + i); free_netdev(net); return NULL; } dev_set_drvdata(&pdev->dev, net); return net; } /*! * @brief The function frees can device. * * @param pdev the pointer of platform device. * * @return none */ void flexcan_device_free(struct platform_device *pdev) { struct net_device *net; struct flexcan_device *flexcan; int i, num; net = (struct net_device *)dev_get_drvdata(&pdev->dev); unregister_netdev(net); flexcan = netdev_priv(net); del_timer(&flexcan->timer); num = ARRAY_SIZE(flexcan_dev_attr); for (i = 0; i < num; i++) device_remove_file(&pdev->dev, flexcan_dev_attr + i); flexcan_device_detach(netdev_priv(net)); free_netdev(net); }