// SPDX-License-Identifier: GPL-2.0-only /* * Core driver for the CC770 and AN82527 CAN controllers * * Copyright (C) 2009, 2011 Wolfgang Grandegger */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cc770.h" MODULE_AUTHOR("Wolfgang Grandegger "); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION(KBUILD_MODNAME "CAN netdevice driver"); /* * The CC770 is a CAN controller from Bosch, which is 100% compatible * with the AN82527 from Intel, but with "bugs" being fixed and some * additional functionality, mainly: * * 1. RX and TX error counters are readable. * 2. Support of silent (listen-only) mode. * 3. Message object 15 can receive all types of frames, also RTR and EFF. * * Details are available from Bosch's "CC770_Product_Info_2007-01.pdf", * which explains in detail the compatibility between the CC770 and the * 82527. This driver use the additional functionality 3. on real CC770 * devices. Unfortunately, the CC770 does still not store the message * identifier of received remote transmission request frames and * therefore it's set to 0. * * The message objects 1..14 can be used for TX and RX while the message * objects 15 is optimized for RX. It has a shadow register for reliable * data reception under heavy bus load. Therefore it makes sense to use * this message object for the needed use case. The frame type (EFF/SFF) * for the message object 15 can be defined via kernel module parameter * "msgobj15_eff". If not equal 0, it will receive 29-bit EFF frames, * otherwise 11 bit SFF messages. */ static int msgobj15_eff; module_param(msgobj15_eff, int, 0444); MODULE_PARM_DESC(msgobj15_eff, "Extended 29-bit frames for message object 15 " "(default: 11-bit standard frames)"); static int i82527_compat; module_param(i82527_compat, int, 0444); MODULE_PARM_DESC(i82527_compat, "Strict Intel 82527 compatibility mode " "without using additional functions"); /* * This driver uses the last 5 message objects 11..15. The definitions * and structure below allows to configure and assign them to the real * message object. */ static unsigned char cc770_obj_flags[CC770_OBJ_MAX] = { [CC770_OBJ_RX0] = CC770_OBJ_FLAG_RX, [CC770_OBJ_RX1] = CC770_OBJ_FLAG_RX | CC770_OBJ_FLAG_EFF, [CC770_OBJ_RX_RTR0] = CC770_OBJ_FLAG_RX | CC770_OBJ_FLAG_RTR, [CC770_OBJ_RX_RTR1] = CC770_OBJ_FLAG_RX | CC770_OBJ_FLAG_RTR | CC770_OBJ_FLAG_EFF, [CC770_OBJ_TX] = 0, }; static const struct can_bittiming_const cc770_bittiming_const = { .name = KBUILD_MODNAME, .tseg1_min = 1, .tseg1_max = 16, .tseg2_min = 1, .tseg2_max = 8, .sjw_max = 4, .brp_min = 1, .brp_max = 64, .brp_inc = 1, }; static inline int intid2obj(unsigned int intid) { if (intid == 2) return 0; else return MSGOBJ_LAST + 2 - intid; } static void enable_all_objs(const struct net_device *dev) { struct cc770_priv *priv = netdev_priv(dev); u8 msgcfg; unsigned char obj_flags; unsigned int o, mo; for (o = 0; o < ARRAY_SIZE(priv->obj_flags); o++) { obj_flags = priv->obj_flags[o]; mo = obj2msgobj(o); if (obj_flags & CC770_OBJ_FLAG_RX) { /* * We don't need extra objects for RTR and EFF if * the additional CC770 functions are enabled. */ if (priv->control_normal_mode & CTRL_EAF) { if (o > 0) continue; netdev_dbg(dev, "Message object %d for " "RX data, RTR, SFF and EFF\n", mo); } else { netdev_dbg(dev, "Message object %d for RX %s %s\n", mo, obj_flags & CC770_OBJ_FLAG_RTR ? "RTR" : "data", obj_flags & CC770_OBJ_FLAG_EFF ? "EFF" : "SFF"); } if (obj_flags & CC770_OBJ_FLAG_EFF) msgcfg = MSGCFG_XTD; else msgcfg = 0; if (obj_flags & CC770_OBJ_FLAG_RTR) msgcfg |= MSGCFG_DIR; cc770_write_reg(priv, msgobj[mo].config, msgcfg); cc770_write_reg(priv, msgobj[mo].ctrl0, MSGVAL_SET | TXIE_RES | RXIE_SET | INTPND_RES); if (obj_flags & CC770_OBJ_FLAG_RTR) cc770_write_reg(priv, msgobj[mo].ctrl1, NEWDAT_RES | CPUUPD_SET | TXRQST_RES | RMTPND_RES); else cc770_write_reg(priv, msgobj[mo].ctrl1, NEWDAT_RES | MSGLST_RES | TXRQST_RES | RMTPND_RES); } else { netdev_dbg(dev, "Message object %d for " "TX data, RTR, SFF and EFF\n", mo); cc770_write_reg(priv, msgobj[mo].ctrl1, RMTPND_RES | TXRQST_RES | CPUUPD_RES | NEWDAT_RES); cc770_write_reg(priv, msgobj[mo].ctrl0, MSGVAL_RES | TXIE_RES | RXIE_RES | INTPND_RES); } } } static void disable_all_objs(const struct cc770_priv *priv) { int o, mo; for (o = 0; o < ARRAY_SIZE(priv->obj_flags); o++) { mo = obj2msgobj(o); if (priv->obj_flags[o] & CC770_OBJ_FLAG_RX) { if (o > 0 && priv->control_normal_mode & CTRL_EAF) continue; cc770_write_reg(priv, msgobj[mo].ctrl1, NEWDAT_RES | MSGLST_RES | TXRQST_RES | RMTPND_RES); cc770_write_reg(priv, msgobj[mo].ctrl0, MSGVAL_RES | TXIE_RES | RXIE_RES | INTPND_RES); } else { /* Clear message object for send */ cc770_write_reg(priv, msgobj[mo].ctrl1, RMTPND_RES | TXRQST_RES | CPUUPD_RES | NEWDAT_RES); cc770_write_reg(priv, msgobj[mo].ctrl0, MSGVAL_RES | TXIE_RES | RXIE_RES | INTPND_RES); } } } static void set_reset_mode(struct net_device *dev) { struct cc770_priv *priv = netdev_priv(dev); /* Enable configuration and puts chip in bus-off, disable interrupts */ cc770_write_reg(priv, control, CTRL_CCE | CTRL_INI); priv->can.state = CAN_STATE_STOPPED; /* Clear interrupts */ cc770_read_reg(priv, interrupt); /* Clear status register */ cc770_write_reg(priv, status, 0); /* Disable all used message objects */ disable_all_objs(priv); } static void set_normal_mode(struct net_device *dev) { struct cc770_priv *priv = netdev_priv(dev); /* Clear interrupts */ cc770_read_reg(priv, interrupt); /* Clear status register and pre-set last error code */ cc770_write_reg(priv, status, STAT_LEC_MASK); /* Enable all used message objects*/ enable_all_objs(dev); /* * Clear bus-off, interrupts only for errors, * not for status change */ cc770_write_reg(priv, control, priv->control_normal_mode); priv->can.state = CAN_STATE_ERROR_ACTIVE; } static void chipset_init(struct cc770_priv *priv) { int mo, id, data; /* Enable configuration and put chip in bus-off, disable interrupts */ cc770_write_reg(priv, control, (CTRL_CCE | CTRL_INI)); /* Set CLKOUT divider and slew rates */ cc770_write_reg(priv, clkout, priv->clkout); /* Configure CPU interface / CLKOUT enable */ cc770_write_reg(priv, cpu_interface, priv->cpu_interface); /* Set bus configuration */ cc770_write_reg(priv, bus_config, priv->bus_config); /* Clear interrupts */ cc770_read_reg(priv, interrupt); /* Clear status register */ cc770_write_reg(priv, status, 0); /* Clear and invalidate message objects */ for (mo = MSGOBJ_FIRST; mo <= MSGOBJ_LAST; mo++) { cc770_write_reg(priv, msgobj[mo].ctrl0, INTPND_UNC | RXIE_RES | TXIE_RES | MSGVAL_RES); cc770_write_reg(priv, msgobj[mo].ctrl0, INTPND_RES | RXIE_RES | TXIE_RES | MSGVAL_RES); cc770_write_reg(priv, msgobj[mo].ctrl1, NEWDAT_RES | MSGLST_RES | TXRQST_RES | RMTPND_RES); for (data = 0; data < 8; data++) cc770_write_reg(priv, msgobj[mo].data[data], 0); for (id = 0; id < 4; id++) cc770_write_reg(priv, msgobj[mo].id[id], 0); cc770_write_reg(priv, msgobj[mo].config, 0); } /* Set all global ID masks to "don't care" */ cc770_write_reg(priv, global_mask_std[0], 0); cc770_write_reg(priv, global_mask_std[1], 0); cc770_write_reg(priv, global_mask_ext[0], 0); cc770_write_reg(priv, global_mask_ext[1], 0); cc770_write_reg(priv, global_mask_ext[2], 0); cc770_write_reg(priv, global_mask_ext[3], 0); } static int cc770_probe_chip(struct net_device *dev) { struct cc770_priv *priv = netdev_priv(dev); /* Enable configuration, put chip in bus-off, disable ints */ cc770_write_reg(priv, control, CTRL_CCE | CTRL_EAF | CTRL_INI); /* Configure cpu interface / CLKOUT disable */ cc770_write_reg(priv, cpu_interface, priv->cpu_interface); /* * Check if hardware reset is still inactive or maybe there * is no chip in this address space */ if (cc770_read_reg(priv, cpu_interface) & CPUIF_RST) { netdev_info(dev, "probing @0x%p failed (reset)\n", priv->reg_base); return -ENODEV; } /* Write and read back test pattern (some arbitrary values) */ cc770_write_reg(priv, msgobj[1].data[1], 0x25); cc770_write_reg(priv, msgobj[2].data[3], 0x52); cc770_write_reg(priv, msgobj[10].data[6], 0xc3); if ((cc770_read_reg(priv, msgobj[1].data[1]) != 0x25) || (cc770_read_reg(priv, msgobj[2].data[3]) != 0x52) || (cc770_read_reg(priv, msgobj[10].data[6]) != 0xc3)) { netdev_info(dev, "probing @0x%p failed (pattern)\n", priv->reg_base); return -ENODEV; } /* Check if this chip is a CC770 supporting additional functions */ if (cc770_read_reg(priv, control) & CTRL_EAF) priv->control_normal_mode |= CTRL_EAF; return 0; } static void cc770_start(struct net_device *dev) { struct cc770_priv *priv = netdev_priv(dev); /* leave reset mode */ if (priv->can.state != CAN_STATE_STOPPED) set_reset_mode(dev); /* leave reset mode */ set_normal_mode(dev); } static int cc770_set_mode(struct net_device *dev, enum can_mode mode) { switch (mode) { case CAN_MODE_START: cc770_start(dev); netif_wake_queue(dev); break; default: return -EOPNOTSUPP; } return 0; } static int cc770_set_bittiming(struct net_device *dev) { struct cc770_priv *priv = netdev_priv(dev); struct can_bittiming *bt = &priv->can.bittiming; u8 btr0, btr1; btr0 = ((bt->brp - 1) & 0x3f) | (((bt->sjw - 1) & 0x3) << 6); btr1 = ((bt->prop_seg + bt->phase_seg1 - 1) & 0xf) | (((bt->phase_seg2 - 1) & 0x7) << 4); if (priv->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES) btr1 |= 0x80; netdev_info(dev, "setting BTR0=0x%02x BTR1=0x%02x\n", btr0, btr1); cc770_write_reg(priv, bit_timing_0, btr0); cc770_write_reg(priv, bit_timing_1, btr1); return 0; } static int cc770_get_berr_counter(const struct net_device *dev, struct can_berr_counter *bec) { struct cc770_priv *priv = netdev_priv(dev); bec->txerr = cc770_read_reg(priv, tx_error_counter); bec->rxerr = cc770_read_reg(priv, rx_error_counter); return 0; } static void cc770_tx(struct net_device *dev, int mo) { struct cc770_priv *priv = netdev_priv(dev); struct can_frame *cf = (struct can_frame *)priv->tx_skb->data; u8 dlc, rtr; u32 id; int i; dlc = cf->len; id = cf->can_id; rtr = cf->can_id & CAN_RTR_FLAG ? 0 : MSGCFG_DIR; cc770_write_reg(priv, msgobj[mo].ctrl0, MSGVAL_RES | TXIE_RES | RXIE_RES | INTPND_RES); cc770_write_reg(priv, msgobj[mo].ctrl1, RMTPND_RES | TXRQST_RES | CPUUPD_SET | NEWDAT_RES); if (id & CAN_EFF_FLAG) { id &= CAN_EFF_MASK; cc770_write_reg(priv, msgobj[mo].config, (dlc << 4) | rtr | MSGCFG_XTD); cc770_write_reg(priv, msgobj[mo].id[3], id << 3); cc770_write_reg(priv, msgobj[mo].id[2], id >> 5); cc770_write_reg(priv, msgobj[mo].id[1], id >> 13); cc770_write_reg(priv, msgobj[mo].id[0], id >> 21); } else { id &= CAN_SFF_MASK; cc770_write_reg(priv, msgobj[mo].config, (dlc << 4) | rtr); cc770_write_reg(priv, msgobj[mo].id[0], id >> 3); cc770_write_reg(priv, msgobj[mo].id[1], id << 5); } for (i = 0; i < dlc; i++) cc770_write_reg(priv, msgobj[mo].data[i], cf->data[i]); cc770_write_reg(priv, msgobj[mo].ctrl1, RMTPND_UNC | TXRQST_SET | CPUUPD_RES | NEWDAT_UNC); cc770_write_reg(priv, msgobj[mo].ctrl0, MSGVAL_SET | TXIE_SET | RXIE_SET | INTPND_UNC); } static netdev_tx_t cc770_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct cc770_priv *priv = netdev_priv(dev); unsigned int mo = obj2msgobj(CC770_OBJ_TX); if (can_dropped_invalid_skb(dev, skb)) return NETDEV_TX_OK; netif_stop_queue(dev); if ((cc770_read_reg(priv, msgobj[mo].ctrl1) & TXRQST_UNC) == TXRQST_SET) { netdev_err(dev, "TX register is still occupied!\n"); return NETDEV_TX_BUSY; } priv->tx_skb = skb; cc770_tx(dev, mo); return NETDEV_TX_OK; } static void cc770_rx(struct net_device *dev, unsigned int mo, u8 ctrl1) { struct cc770_priv *priv = netdev_priv(dev); struct net_device_stats *stats = &dev->stats; struct can_frame *cf; struct sk_buff *skb; u8 config; u32 id; int i; skb = alloc_can_skb(dev, &cf); if (!skb) return; config = cc770_read_reg(priv, msgobj[mo].config); if (ctrl1 & RMTPND_SET) { /* * Unfortunately, the chip does not store the real message * identifier of the received remote transmission request * frame. Therefore we set it to 0. */ cf->can_id = CAN_RTR_FLAG; if (config & MSGCFG_XTD) cf->can_id |= CAN_EFF_FLAG; cf->len = 0; } else { if (config & MSGCFG_XTD) { id = cc770_read_reg(priv, msgobj[mo].id[3]); id |= cc770_read_reg(priv, msgobj[mo].id[2]) << 8; id |= cc770_read_reg(priv, msgobj[mo].id[1]) << 16; id |= cc770_read_reg(priv, msgobj[mo].id[0]) << 24; id >>= 3; id |= CAN_EFF_FLAG; } else { id = cc770_read_reg(priv, msgobj[mo].id[1]); id |= cc770_read_reg(priv, msgobj[mo].id[0]) << 8; id >>= 5; } cf->can_id = id; cf->len = can_cc_dlc2len((config & 0xf0) >> 4); for (i = 0; i < cf->len; i++) cf->data[i] = cc770_read_reg(priv, msgobj[mo].data[i]); } stats->rx_packets++; stats->rx_bytes += cf->len; netif_rx(skb); } static int cc770_err(struct net_device *dev, u8 status) { struct cc770_priv *priv = netdev_priv(dev); struct net_device_stats *stats = &dev->stats; struct can_frame *cf; struct sk_buff *skb; u8 lec; netdev_dbg(dev, "status interrupt (%#x)\n", status); skb = alloc_can_err_skb(dev, &cf); if (!skb) return -ENOMEM; /* Use extended functions of the CC770 */ if (priv->control_normal_mode & CTRL_EAF) { cf->data[6] = cc770_read_reg(priv, tx_error_counter); cf->data[7] = cc770_read_reg(priv, rx_error_counter); } if (status & STAT_BOFF) { /* Disable interrupts */ cc770_write_reg(priv, control, CTRL_INI); cf->can_id |= CAN_ERR_BUSOFF; priv->can.state = CAN_STATE_BUS_OFF; priv->can.can_stats.bus_off++; can_bus_off(dev); } else if (status & STAT_WARN) { cf->can_id |= CAN_ERR_CRTL; /* Only the CC770 does show error passive */ if (cf->data[7] > 127) { cf->data[1] = CAN_ERR_CRTL_RX_PASSIVE | CAN_ERR_CRTL_TX_PASSIVE; priv->can.state = CAN_STATE_ERROR_PASSIVE; priv->can.can_stats.error_passive++; } else { cf->data[1] = CAN_ERR_CRTL_RX_WARNING | CAN_ERR_CRTL_TX_WARNING; priv->can.state = CAN_STATE_ERROR_WARNING; priv->can.can_stats.error_warning++; } } else { /* Back to error active */ cf->can_id |= CAN_ERR_PROT; cf->data[2] = CAN_ERR_PROT_ACTIVE; priv->can.state = CAN_STATE_ERROR_ACTIVE; } lec = status & STAT_LEC_MASK; if (lec < 7 && lec > 0) { if (lec == STAT_LEC_ACK) { cf->can_id |= CAN_ERR_ACK; } else { cf->can_id |= CAN_ERR_PROT; switch (lec) { case STAT_LEC_STUFF: cf->data[2] |= CAN_ERR_PROT_STUFF; break; case STAT_LEC_FORM: cf->data[2] |= CAN_ERR_PROT_FORM; break; case STAT_LEC_BIT1: cf->data[2] |= CAN_ERR_PROT_BIT1; break; case STAT_LEC_BIT0: cf->data[2] |= CAN_ERR_PROT_BIT0; break; case STAT_LEC_CRC: cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ; break; } } } stats->rx_packets++; stats->rx_bytes += cf->len; netif_rx(skb); return 0; } static int cc770_status_interrupt(struct net_device *dev) { struct cc770_priv *priv = netdev_priv(dev); u8 status; status = cc770_read_reg(priv, status); /* Reset the status register including RXOK and TXOK */ cc770_write_reg(priv, status, STAT_LEC_MASK); if (status & (STAT_WARN | STAT_BOFF) || (status & STAT_LEC_MASK) != STAT_LEC_MASK) { cc770_err(dev, status); return status & STAT_BOFF; } return 0; } static void cc770_rx_interrupt(struct net_device *dev, unsigned int o) { struct cc770_priv *priv = netdev_priv(dev); struct net_device_stats *stats = &dev->stats; unsigned int mo = obj2msgobj(o); u8 ctrl1; int n = CC770_MAX_MSG; while (n--) { ctrl1 = cc770_read_reg(priv, msgobj[mo].ctrl1); if (!(ctrl1 & NEWDAT_SET)) { /* Check for RTR if additional functions are enabled */ if (priv->control_normal_mode & CTRL_EAF) { if (!(cc770_read_reg(priv, msgobj[mo].ctrl0) & INTPND_SET)) break; } else { break; } } if (ctrl1 & MSGLST_SET) { stats->rx_over_errors++; stats->rx_errors++; } if (mo < MSGOBJ_LAST) cc770_write_reg(priv, msgobj[mo].ctrl1, NEWDAT_RES | MSGLST_RES | TXRQST_UNC | RMTPND_UNC); cc770_rx(dev, mo, ctrl1); cc770_write_reg(priv, msgobj[mo].ctrl0, MSGVAL_SET | TXIE_RES | RXIE_SET | INTPND_RES); cc770_write_reg(priv, msgobj[mo].ctrl1, NEWDAT_RES | MSGLST_RES | TXRQST_RES | RMTPND_RES); } } static void cc770_rtr_interrupt(struct net_device *dev, unsigned int o) { struct cc770_priv *priv = netdev_priv(dev); unsigned int mo = obj2msgobj(o); u8 ctrl0, ctrl1; int n = CC770_MAX_MSG; while (n--) { ctrl0 = cc770_read_reg(priv, msgobj[mo].ctrl0); if (!(ctrl0 & INTPND_SET)) break; ctrl1 = cc770_read_reg(priv, msgobj[mo].ctrl1); cc770_rx(dev, mo, ctrl1); cc770_write_reg(priv, msgobj[mo].ctrl0, MSGVAL_SET | TXIE_RES | RXIE_SET | INTPND_RES); cc770_write_reg(priv, msgobj[mo].ctrl1, NEWDAT_RES | CPUUPD_SET | TXRQST_RES | RMTPND_RES); } } static void cc770_tx_interrupt(struct net_device *dev, unsigned int o) { struct cc770_priv *priv = netdev_priv(dev); struct net_device_stats *stats = &dev->stats; unsigned int mo = obj2msgobj(o); struct can_frame *cf; u8 ctrl1; ctrl1 = cc770_read_reg(priv, msgobj[mo].ctrl1); cc770_write_reg(priv, msgobj[mo].ctrl0, MSGVAL_RES | TXIE_RES | RXIE_RES | INTPND_RES); cc770_write_reg(priv, msgobj[mo].ctrl1, RMTPND_RES | TXRQST_RES | MSGLST_RES | NEWDAT_RES); if (unlikely(!priv->tx_skb)) { netdev_err(dev, "missing tx skb in tx interrupt\n"); return; } if (unlikely(ctrl1 & MSGLST_SET)) { stats->rx_over_errors++; stats->rx_errors++; } /* When the CC770 is sending an RTR message and it receives a regular * message that matches the id of the RTR message, it will overwrite the * outgoing message in the TX register. When this happens we must * process the received message and try to transmit the outgoing skb * again. */ if (unlikely(ctrl1 & NEWDAT_SET)) { cc770_rx(dev, mo, ctrl1); cc770_tx(dev, mo); return; } cf = (struct can_frame *)priv->tx_skb->data; stats->tx_bytes += cf->len; stats->tx_packets++; can_put_echo_skb(priv->tx_skb, dev, 0, 0); can_get_echo_skb(dev, 0, NULL); priv->tx_skb = NULL; netif_wake_queue(dev); } static irqreturn_t cc770_interrupt(int irq, void *dev_id) { struct net_device *dev = (struct net_device *)dev_id; struct cc770_priv *priv = netdev_priv(dev); u8 intid; int o, n = 0; /* Shared interrupts and IRQ off? */ if (priv->can.state == CAN_STATE_STOPPED) return IRQ_NONE; if (priv->pre_irq) priv->pre_irq(priv); while (n < CC770_MAX_IRQ) { /* Read the highest pending interrupt request */ intid = cc770_read_reg(priv, interrupt); if (!intid) break; n++; if (intid == 1) { /* Exit in case of bus-off */ if (cc770_status_interrupt(dev)) break; } else { o = intid2obj(intid); if (o >= CC770_OBJ_MAX) { netdev_err(dev, "Unexpected interrupt id %d\n", intid); continue; } if (priv->obj_flags[o] & CC770_OBJ_FLAG_RTR) cc770_rtr_interrupt(dev, o); else if (priv->obj_flags[o] & CC770_OBJ_FLAG_RX) cc770_rx_interrupt(dev, o); else cc770_tx_interrupt(dev, o); } } if (priv->post_irq) priv->post_irq(priv); if (n >= CC770_MAX_IRQ) netdev_dbg(dev, "%d messages handled in ISR", n); return (n) ? IRQ_HANDLED : IRQ_NONE; } static int cc770_open(struct net_device *dev) { struct cc770_priv *priv = netdev_priv(dev); int err; /* set chip into reset mode */ set_reset_mode(dev); /* common open */ err = open_candev(dev); if (err) return err; err = request_irq(dev->irq, &cc770_interrupt, priv->irq_flags, dev->name, dev); if (err) { close_candev(dev); return -EAGAIN; } /* init and start chip */ cc770_start(dev); netif_start_queue(dev); return 0; } static int cc770_close(struct net_device *dev) { netif_stop_queue(dev); set_reset_mode(dev); free_irq(dev->irq, dev); close_candev(dev); return 0; } struct net_device *alloc_cc770dev(int sizeof_priv) { struct net_device *dev; struct cc770_priv *priv; dev = alloc_candev(sizeof(struct cc770_priv) + sizeof_priv, CC770_ECHO_SKB_MAX); if (!dev) return NULL; priv = netdev_priv(dev); priv->dev = dev; priv->can.bittiming_const = &cc770_bittiming_const; priv->can.do_set_bittiming = cc770_set_bittiming; priv->can.do_set_mode = cc770_set_mode; priv->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES; priv->tx_skb = NULL; memcpy(priv->obj_flags, cc770_obj_flags, sizeof(cc770_obj_flags)); if (sizeof_priv) priv->priv = (void *)priv + sizeof(struct cc770_priv); return dev; } EXPORT_SYMBOL_GPL(alloc_cc770dev); void free_cc770dev(struct net_device *dev) { free_candev(dev); } EXPORT_SYMBOL_GPL(free_cc770dev); static const struct net_device_ops cc770_netdev_ops = { .ndo_open = cc770_open, .ndo_stop = cc770_close, .ndo_start_xmit = cc770_start_xmit, .ndo_change_mtu = can_change_mtu, }; int register_cc770dev(struct net_device *dev) { struct cc770_priv *priv = netdev_priv(dev); int err; err = cc770_probe_chip(dev); if (err) return err; dev->netdev_ops = &cc770_netdev_ops; dev->flags |= IFF_ECHO; /* we support local echo */ /* Should we use additional functions? */ if (!i82527_compat && priv->control_normal_mode & CTRL_EAF) { priv->can.do_get_berr_counter = cc770_get_berr_counter; priv->control_normal_mode = CTRL_IE | CTRL_EAF | CTRL_EIE; netdev_dbg(dev, "i82527 mode with additional functions\n"); } else { priv->control_normal_mode = CTRL_IE | CTRL_EIE; netdev_dbg(dev, "strict i82527 compatibility mode\n"); } chipset_init(priv); set_reset_mode(dev); return register_candev(dev); } EXPORT_SYMBOL_GPL(register_cc770dev); void unregister_cc770dev(struct net_device *dev) { set_reset_mode(dev); unregister_candev(dev); } EXPORT_SYMBOL_GPL(unregister_cc770dev); static __init int cc770_init(void) { if (msgobj15_eff) { cc770_obj_flags[CC770_OBJ_RX0] |= CC770_OBJ_FLAG_EFF; cc770_obj_flags[CC770_OBJ_RX1] &= ~CC770_OBJ_FLAG_EFF; } pr_info("CAN netdevice driver\n"); return 0; } module_init(cc770_init); static __exit void cc770_exit(void) { pr_info("driver removed\n"); } module_exit(cc770_exit);