| /* |
| * CAN bus driver for IFI CANFD controller |
| * |
| * Copyright (C) 2016 Marek Vasut <marex@denx.de> |
| * |
| * Details about this controller can be found at |
| * http://www.ifi-pld.de/IP/CANFD/canfd.html |
| * |
| * This file is licensed under the terms of the GNU General Public |
| * License version 2. This program is licensed "as is" without any |
| * warranty of any kind, whether express or implied. |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/delay.h> |
| #include <linux/ethtool.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/netdevice.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| |
| #include <linux/can/dev.h> |
| |
| #define IFI_CANFD_STCMD 0x0 |
| #define IFI_CANFD_STCMD_HARDRESET 0xDEADCAFD |
| #define IFI_CANFD_STCMD_ENABLE BIT(0) |
| #define IFI_CANFD_STCMD_ERROR_ACTIVE BIT(2) |
| #define IFI_CANFD_STCMD_ERROR_PASSIVE BIT(3) |
| #define IFI_CANFD_STCMD_BUSOFF BIT(4) |
| #define IFI_CANFD_STCMD_ERROR_WARNING BIT(5) |
| #define IFI_CANFD_STCMD_BUSMONITOR BIT(16) |
| #define IFI_CANFD_STCMD_LOOPBACK BIT(18) |
| #define IFI_CANFD_STCMD_DISABLE_CANFD BIT(24) |
| #define IFI_CANFD_STCMD_ENABLE_ISO BIT(25) |
| #define IFI_CANFD_STCMD_ENABLE_7_9_8_8_TIMING BIT(26) |
| #define IFI_CANFD_STCMD_NORMAL_MODE ((u32)BIT(31)) |
| |
| #define IFI_CANFD_RXSTCMD 0x4 |
| #define IFI_CANFD_RXSTCMD_REMOVE_MSG BIT(0) |
| #define IFI_CANFD_RXSTCMD_RESET BIT(7) |
| #define IFI_CANFD_RXSTCMD_EMPTY BIT(8) |
| #define IFI_CANFD_RXSTCMD_OVERFLOW BIT(13) |
| |
| #define IFI_CANFD_TXSTCMD 0x8 |
| #define IFI_CANFD_TXSTCMD_ADD_MSG BIT(0) |
| #define IFI_CANFD_TXSTCMD_HIGH_PRIO BIT(1) |
| #define IFI_CANFD_TXSTCMD_RESET BIT(7) |
| #define IFI_CANFD_TXSTCMD_EMPTY BIT(8) |
| #define IFI_CANFD_TXSTCMD_FULL BIT(12) |
| #define IFI_CANFD_TXSTCMD_OVERFLOW BIT(13) |
| |
| #define IFI_CANFD_INTERRUPT 0xc |
| #define IFI_CANFD_INTERRUPT_ERROR_BUSOFF BIT(0) |
| #define IFI_CANFD_INTERRUPT_ERROR_WARNING BIT(1) |
| #define IFI_CANFD_INTERRUPT_ERROR_STATE_CHG BIT(2) |
| #define IFI_CANFD_INTERRUPT_ERROR_REC_TEC_INC BIT(3) |
| #define IFI_CANFD_INTERRUPT_ERROR_COUNTER BIT(10) |
| #define IFI_CANFD_INTERRUPT_TXFIFO_EMPTY BIT(16) |
| #define IFI_CANFD_INTERRUPT_TXFIFO_REMOVE BIT(22) |
| #define IFI_CANFD_INTERRUPT_RXFIFO_NEMPTY BIT(24) |
| #define IFI_CANFD_INTERRUPT_RXFIFO_NEMPTY_PER BIT(25) |
| #define IFI_CANFD_INTERRUPT_SET_IRQ ((u32)BIT(31)) |
| |
| #define IFI_CANFD_IRQMASK 0x10 |
| #define IFI_CANFD_IRQMASK_ERROR_BUSOFF BIT(0) |
| #define IFI_CANFD_IRQMASK_ERROR_WARNING BIT(1) |
| #define IFI_CANFD_IRQMASK_ERROR_STATE_CHG BIT(2) |
| #define IFI_CANFD_IRQMASK_ERROR_REC_TEC_INC BIT(3) |
| #define IFI_CANFD_IRQMASK_SET_ERR BIT(7) |
| #define IFI_CANFD_IRQMASK_SET_TS BIT(15) |
| #define IFI_CANFD_IRQMASK_TXFIFO_EMPTY BIT(16) |
| #define IFI_CANFD_IRQMASK_SET_TX BIT(23) |
| #define IFI_CANFD_IRQMASK_RXFIFO_NEMPTY BIT(24) |
| #define IFI_CANFD_IRQMASK_SET_RX ((u32)BIT(31)) |
| |
| #define IFI_CANFD_TIME 0x14 |
| #define IFI_CANFD_FTIME 0x18 |
| #define IFI_CANFD_TIME_TIMEB_OFF 0 |
| #define IFI_CANFD_TIME_TIMEA_OFF 8 |
| #define IFI_CANFD_TIME_PRESCALE_OFF 16 |
| #define IFI_CANFD_TIME_SJW_OFF_7_9_8_8 25 |
| #define IFI_CANFD_TIME_SJW_OFF_4_12_6_6 28 |
| #define IFI_CANFD_TIME_SET_SJW_4_12_6_6 BIT(6) |
| #define IFI_CANFD_TIME_SET_TIMEB_4_12_6_6 BIT(7) |
| #define IFI_CANFD_TIME_SET_PRESC_4_12_6_6 BIT(14) |
| #define IFI_CANFD_TIME_SET_TIMEA_4_12_6_6 BIT(15) |
| |
| #define IFI_CANFD_TDELAY 0x1c |
| #define IFI_CANFD_TDELAY_DEFAULT 0xb |
| #define IFI_CANFD_TDELAY_MASK 0x3fff |
| #define IFI_CANFD_TDELAY_ABS BIT(14) |
| #define IFI_CANFD_TDELAY_EN BIT(15) |
| |
| #define IFI_CANFD_ERROR 0x20 |
| #define IFI_CANFD_ERROR_TX_OFFSET 0 |
| #define IFI_CANFD_ERROR_TX_MASK 0xff |
| #define IFI_CANFD_ERROR_RX_OFFSET 16 |
| #define IFI_CANFD_ERROR_RX_MASK 0xff |
| |
| #define IFI_CANFD_ERRCNT 0x24 |
| |
| #define IFI_CANFD_SUSPEND 0x28 |
| |
| #define IFI_CANFD_REPEAT 0x2c |
| |
| #define IFI_CANFD_TRAFFIC 0x30 |
| |
| #define IFI_CANFD_TSCONTROL 0x34 |
| |
| #define IFI_CANFD_TSC 0x38 |
| |
| #define IFI_CANFD_TST 0x3c |
| |
| #define IFI_CANFD_RES1 0x40 |
| |
| #define IFI_CANFD_ERROR_CTR 0x44 |
| #define IFI_CANFD_ERROR_CTR_UNLOCK_MAGIC 0x21302899 |
| #define IFI_CANFD_ERROR_CTR_OVERLOAD_FIRST BIT(0) |
| #define IFI_CANFD_ERROR_CTR_ACK_ERROR_FIRST BIT(1) |
| #define IFI_CANFD_ERROR_CTR_BIT0_ERROR_FIRST BIT(2) |
| #define IFI_CANFD_ERROR_CTR_BIT1_ERROR_FIRST BIT(3) |
| #define IFI_CANFD_ERROR_CTR_STUFF_ERROR_FIRST BIT(4) |
| #define IFI_CANFD_ERROR_CTR_CRC_ERROR_FIRST BIT(5) |
| #define IFI_CANFD_ERROR_CTR_FORM_ERROR_FIRST BIT(6) |
| #define IFI_CANFD_ERROR_CTR_OVERLOAD_ALL BIT(8) |
| #define IFI_CANFD_ERROR_CTR_ACK_ERROR_ALL BIT(9) |
| #define IFI_CANFD_ERROR_CTR_BIT0_ERROR_ALL BIT(10) |
| #define IFI_CANFD_ERROR_CTR_BIT1_ERROR_ALL BIT(11) |
| #define IFI_CANFD_ERROR_CTR_STUFF_ERROR_ALL BIT(12) |
| #define IFI_CANFD_ERROR_CTR_CRC_ERROR_ALL BIT(13) |
| #define IFI_CANFD_ERROR_CTR_FORM_ERROR_ALL BIT(14) |
| #define IFI_CANFD_ERROR_CTR_BITPOSITION_OFFSET 16 |
| #define IFI_CANFD_ERROR_CTR_BITPOSITION_MASK 0xff |
| #define IFI_CANFD_ERROR_CTR_ER_RESET BIT(30) |
| #define IFI_CANFD_ERROR_CTR_ER_ENABLE ((u32)BIT(31)) |
| |
| #define IFI_CANFD_PAR 0x48 |
| |
| #define IFI_CANFD_CANCLOCK 0x4c |
| |
| #define IFI_CANFD_SYSCLOCK 0x50 |
| |
| #define IFI_CANFD_VER 0x54 |
| #define IFI_CANFD_VER_REV_MASK 0xff |
| #define IFI_CANFD_VER_REV_MIN_SUPPORTED 0x15 |
| |
| #define IFI_CANFD_IP_ID 0x58 |
| #define IFI_CANFD_IP_ID_VALUE 0xD073CAFD |
| |
| #define IFI_CANFD_TEST 0x5c |
| |
| #define IFI_CANFD_RXFIFO_TS_63_32 0x60 |
| |
| #define IFI_CANFD_RXFIFO_TS_31_0 0x64 |
| |
| #define IFI_CANFD_RXFIFO_DLC 0x68 |
| #define IFI_CANFD_RXFIFO_DLC_DLC_OFFSET 0 |
| #define IFI_CANFD_RXFIFO_DLC_DLC_MASK 0xf |
| #define IFI_CANFD_RXFIFO_DLC_RTR BIT(4) |
| #define IFI_CANFD_RXFIFO_DLC_EDL BIT(5) |
| #define IFI_CANFD_RXFIFO_DLC_BRS BIT(6) |
| #define IFI_CANFD_RXFIFO_DLC_ESI BIT(7) |
| #define IFI_CANFD_RXFIFO_DLC_OBJ_OFFSET 8 |
| #define IFI_CANFD_RXFIFO_DLC_OBJ_MASK 0x1ff |
| #define IFI_CANFD_RXFIFO_DLC_FNR_OFFSET 24 |
| #define IFI_CANFD_RXFIFO_DLC_FNR_MASK 0xff |
| |
| #define IFI_CANFD_RXFIFO_ID 0x6c |
| #define IFI_CANFD_RXFIFO_ID_ID_OFFSET 0 |
| #define IFI_CANFD_RXFIFO_ID_ID_STD_MASK CAN_SFF_MASK |
| #define IFI_CANFD_RXFIFO_ID_ID_STD_OFFSET 0 |
| #define IFI_CANFD_RXFIFO_ID_ID_STD_WIDTH 10 |
| #define IFI_CANFD_RXFIFO_ID_ID_XTD_MASK CAN_EFF_MASK |
| #define IFI_CANFD_RXFIFO_ID_ID_XTD_OFFSET 11 |
| #define IFI_CANFD_RXFIFO_ID_ID_XTD_WIDTH 18 |
| #define IFI_CANFD_RXFIFO_ID_IDE BIT(29) |
| |
| #define IFI_CANFD_RXFIFO_DATA 0x70 /* 0x70..0xac */ |
| |
| #define IFI_CANFD_TXFIFO_SUSPEND_US 0xb0 |
| |
| #define IFI_CANFD_TXFIFO_REPEATCOUNT 0xb4 |
| |
| #define IFI_CANFD_TXFIFO_DLC 0xb8 |
| #define IFI_CANFD_TXFIFO_DLC_DLC_OFFSET 0 |
| #define IFI_CANFD_TXFIFO_DLC_DLC_MASK 0xf |
| #define IFI_CANFD_TXFIFO_DLC_RTR BIT(4) |
| #define IFI_CANFD_TXFIFO_DLC_EDL BIT(5) |
| #define IFI_CANFD_TXFIFO_DLC_BRS BIT(6) |
| #define IFI_CANFD_TXFIFO_DLC_FNR_OFFSET 24 |
| #define IFI_CANFD_TXFIFO_DLC_FNR_MASK 0xff |
| |
| #define IFI_CANFD_TXFIFO_ID 0xbc |
| #define IFI_CANFD_TXFIFO_ID_ID_OFFSET 0 |
| #define IFI_CANFD_TXFIFO_ID_ID_STD_MASK CAN_SFF_MASK |
| #define IFI_CANFD_TXFIFO_ID_ID_STD_OFFSET 0 |
| #define IFI_CANFD_TXFIFO_ID_ID_STD_WIDTH 10 |
| #define IFI_CANFD_TXFIFO_ID_ID_XTD_MASK CAN_EFF_MASK |
| #define IFI_CANFD_TXFIFO_ID_ID_XTD_OFFSET 11 |
| #define IFI_CANFD_TXFIFO_ID_ID_XTD_WIDTH 18 |
| #define IFI_CANFD_TXFIFO_ID_IDE BIT(29) |
| |
| #define IFI_CANFD_TXFIFO_DATA 0xc0 /* 0xb0..0xfc */ |
| |
| #define IFI_CANFD_FILTER_MASK(n) (0x800 + ((n) * 8) + 0) |
| #define IFI_CANFD_FILTER_MASK_EXT BIT(29) |
| #define IFI_CANFD_FILTER_MASK_EDL BIT(30) |
| #define IFI_CANFD_FILTER_MASK_VALID ((u32)BIT(31)) |
| |
| #define IFI_CANFD_FILTER_IDENT(n) (0x800 + ((n) * 8) + 4) |
| #define IFI_CANFD_FILTER_IDENT_IDE BIT(29) |
| #define IFI_CANFD_FILTER_IDENT_CANFD BIT(30) |
| #define IFI_CANFD_FILTER_IDENT_VALID ((u32)BIT(31)) |
| |
| /* IFI CANFD private data structure */ |
| struct ifi_canfd_priv { |
| struct can_priv can; /* must be the first member */ |
| struct napi_struct napi; |
| struct net_device *ndev; |
| void __iomem *base; |
| }; |
| |
| static void ifi_canfd_irq_enable(struct net_device *ndev, bool enable) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| u32 enirq = 0; |
| |
| if (enable) { |
| enirq = IFI_CANFD_IRQMASK_TXFIFO_EMPTY | |
| IFI_CANFD_IRQMASK_RXFIFO_NEMPTY | |
| IFI_CANFD_IRQMASK_ERROR_STATE_CHG | |
| IFI_CANFD_IRQMASK_ERROR_WARNING | |
| IFI_CANFD_IRQMASK_ERROR_BUSOFF; |
| if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) |
| enirq |= IFI_CANFD_INTERRUPT_ERROR_COUNTER; |
| } |
| |
| writel(IFI_CANFD_IRQMASK_SET_ERR | |
| IFI_CANFD_IRQMASK_SET_TS | |
| IFI_CANFD_IRQMASK_SET_TX | |
| IFI_CANFD_IRQMASK_SET_RX | enirq, |
| priv->base + IFI_CANFD_IRQMASK); |
| } |
| |
| static void ifi_canfd_read_fifo(struct net_device *ndev) |
| { |
| struct net_device_stats *stats = &ndev->stats; |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| struct canfd_frame *cf; |
| struct sk_buff *skb; |
| const u32 rx_irq_mask = IFI_CANFD_INTERRUPT_RXFIFO_NEMPTY | |
| IFI_CANFD_INTERRUPT_RXFIFO_NEMPTY_PER; |
| u32 rxdlc, rxid; |
| u32 dlc, id; |
| int i; |
| |
| rxdlc = readl(priv->base + IFI_CANFD_RXFIFO_DLC); |
| if (rxdlc & IFI_CANFD_RXFIFO_DLC_EDL) |
| skb = alloc_canfd_skb(ndev, &cf); |
| else |
| skb = alloc_can_skb(ndev, (struct can_frame **)&cf); |
| |
| if (!skb) { |
| stats->rx_dropped++; |
| return; |
| } |
| |
| dlc = (rxdlc >> IFI_CANFD_RXFIFO_DLC_DLC_OFFSET) & |
| IFI_CANFD_RXFIFO_DLC_DLC_MASK; |
| if (rxdlc & IFI_CANFD_RXFIFO_DLC_EDL) |
| cf->len = can_fd_dlc2len(dlc); |
| else |
| cf->len = can_cc_dlc2len(dlc); |
| |
| rxid = readl(priv->base + IFI_CANFD_RXFIFO_ID); |
| id = (rxid >> IFI_CANFD_RXFIFO_ID_ID_OFFSET); |
| if (id & IFI_CANFD_RXFIFO_ID_IDE) { |
| id &= IFI_CANFD_RXFIFO_ID_ID_XTD_MASK; |
| /* |
| * In case the Extended ID frame is received, the standard |
| * and extended part of the ID are swapped in the register, |
| * so swap them back to obtain the correct ID. |
| */ |
| id = (id >> IFI_CANFD_RXFIFO_ID_ID_XTD_OFFSET) | |
| ((id & IFI_CANFD_RXFIFO_ID_ID_STD_MASK) << |
| IFI_CANFD_RXFIFO_ID_ID_XTD_WIDTH); |
| id |= CAN_EFF_FLAG; |
| } else { |
| id &= IFI_CANFD_RXFIFO_ID_ID_STD_MASK; |
| } |
| cf->can_id = id; |
| |
| if (rxdlc & IFI_CANFD_RXFIFO_DLC_ESI) { |
| cf->flags |= CANFD_ESI; |
| netdev_dbg(ndev, "ESI Error\n"); |
| } |
| |
| if (!(rxdlc & IFI_CANFD_RXFIFO_DLC_EDL) && |
| (rxdlc & IFI_CANFD_RXFIFO_DLC_RTR)) { |
| cf->can_id |= CAN_RTR_FLAG; |
| } else { |
| if (rxdlc & IFI_CANFD_RXFIFO_DLC_BRS) |
| cf->flags |= CANFD_BRS; |
| |
| for (i = 0; i < cf->len; i += 4) { |
| *(u32 *)(cf->data + i) = |
| readl(priv->base + IFI_CANFD_RXFIFO_DATA + i); |
| } |
| |
| stats->rx_bytes += cf->len; |
| } |
| stats->rx_packets++; |
| |
| /* Remove the packet from FIFO */ |
| writel(IFI_CANFD_RXSTCMD_REMOVE_MSG, priv->base + IFI_CANFD_RXSTCMD); |
| writel(rx_irq_mask, priv->base + IFI_CANFD_INTERRUPT); |
| |
| netif_receive_skb(skb); |
| } |
| |
| static int ifi_canfd_do_rx_poll(struct net_device *ndev, int quota) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| u32 pkts = 0; |
| u32 rxst; |
| |
| rxst = readl(priv->base + IFI_CANFD_RXSTCMD); |
| if (rxst & IFI_CANFD_RXSTCMD_EMPTY) { |
| netdev_dbg(ndev, "No messages in RX FIFO\n"); |
| return 0; |
| } |
| |
| for (;;) { |
| if (rxst & IFI_CANFD_RXSTCMD_EMPTY) |
| break; |
| if (quota <= 0) |
| break; |
| |
| ifi_canfd_read_fifo(ndev); |
| quota--; |
| pkts++; |
| rxst = readl(priv->base + IFI_CANFD_RXSTCMD); |
| } |
| |
| return pkts; |
| } |
| |
| static int ifi_canfd_handle_lost_msg(struct net_device *ndev) |
| { |
| struct net_device_stats *stats = &ndev->stats; |
| struct sk_buff *skb; |
| struct can_frame *frame; |
| |
| netdev_err(ndev, "RX FIFO overflow, message(s) lost.\n"); |
| |
| stats->rx_errors++; |
| stats->rx_over_errors++; |
| |
| skb = alloc_can_err_skb(ndev, &frame); |
| if (unlikely(!skb)) |
| return 0; |
| |
| frame->can_id |= CAN_ERR_CRTL; |
| frame->data[1] = CAN_ERR_CRTL_RX_OVERFLOW; |
| |
| netif_receive_skb(skb); |
| |
| return 1; |
| } |
| |
| static int ifi_canfd_handle_lec_err(struct net_device *ndev) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| struct net_device_stats *stats = &ndev->stats; |
| struct can_frame *cf; |
| struct sk_buff *skb; |
| u32 errctr = readl(priv->base + IFI_CANFD_ERROR_CTR); |
| const u32 errmask = IFI_CANFD_ERROR_CTR_OVERLOAD_FIRST | |
| IFI_CANFD_ERROR_CTR_ACK_ERROR_FIRST | |
| IFI_CANFD_ERROR_CTR_BIT0_ERROR_FIRST | |
| IFI_CANFD_ERROR_CTR_BIT1_ERROR_FIRST | |
| IFI_CANFD_ERROR_CTR_STUFF_ERROR_FIRST | |
| IFI_CANFD_ERROR_CTR_CRC_ERROR_FIRST | |
| IFI_CANFD_ERROR_CTR_FORM_ERROR_FIRST; |
| |
| if (!(errctr & errmask)) /* No error happened. */ |
| return 0; |
| |
| priv->can.can_stats.bus_error++; |
| stats->rx_errors++; |
| |
| /* Propagate the error condition to the CAN stack. */ |
| skb = alloc_can_err_skb(ndev, &cf); |
| if (unlikely(!skb)) |
| return 0; |
| |
| /* Read the error counter register and check for new errors. */ |
| cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR; |
| |
| if (errctr & IFI_CANFD_ERROR_CTR_OVERLOAD_FIRST) |
| cf->data[2] |= CAN_ERR_PROT_OVERLOAD; |
| |
| if (errctr & IFI_CANFD_ERROR_CTR_ACK_ERROR_FIRST) |
| cf->data[3] = CAN_ERR_PROT_LOC_ACK; |
| |
| if (errctr & IFI_CANFD_ERROR_CTR_BIT0_ERROR_FIRST) |
| cf->data[2] |= CAN_ERR_PROT_BIT0; |
| |
| if (errctr & IFI_CANFD_ERROR_CTR_BIT1_ERROR_FIRST) |
| cf->data[2] |= CAN_ERR_PROT_BIT1; |
| |
| if (errctr & IFI_CANFD_ERROR_CTR_STUFF_ERROR_FIRST) |
| cf->data[2] |= CAN_ERR_PROT_STUFF; |
| |
| if (errctr & IFI_CANFD_ERROR_CTR_CRC_ERROR_FIRST) |
| cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ; |
| |
| if (errctr & IFI_CANFD_ERROR_CTR_FORM_ERROR_FIRST) |
| cf->data[2] |= CAN_ERR_PROT_FORM; |
| |
| /* Reset the error counter, ack the IRQ and re-enable the counter. */ |
| writel(IFI_CANFD_ERROR_CTR_ER_RESET, priv->base + IFI_CANFD_ERROR_CTR); |
| writel(IFI_CANFD_INTERRUPT_ERROR_COUNTER, |
| priv->base + IFI_CANFD_INTERRUPT); |
| writel(IFI_CANFD_ERROR_CTR_ER_ENABLE, priv->base + IFI_CANFD_ERROR_CTR); |
| |
| netif_receive_skb(skb); |
| |
| return 1; |
| } |
| |
| static int ifi_canfd_get_berr_counter(const struct net_device *ndev, |
| struct can_berr_counter *bec) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| u32 err; |
| |
| err = readl(priv->base + IFI_CANFD_ERROR); |
| bec->rxerr = (err >> IFI_CANFD_ERROR_RX_OFFSET) & |
| IFI_CANFD_ERROR_RX_MASK; |
| bec->txerr = (err >> IFI_CANFD_ERROR_TX_OFFSET) & |
| IFI_CANFD_ERROR_TX_MASK; |
| |
| return 0; |
| } |
| |
| static int ifi_canfd_handle_state_change(struct net_device *ndev, |
| enum can_state new_state) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| struct can_frame *cf; |
| struct sk_buff *skb; |
| struct can_berr_counter bec; |
| |
| switch (new_state) { |
| case CAN_STATE_ERROR_ACTIVE: |
| /* error active state */ |
| priv->can.can_stats.error_warning++; |
| priv->can.state = CAN_STATE_ERROR_ACTIVE; |
| break; |
| case CAN_STATE_ERROR_WARNING: |
| /* error warning state */ |
| priv->can.can_stats.error_warning++; |
| priv->can.state = CAN_STATE_ERROR_WARNING; |
| break; |
| case CAN_STATE_ERROR_PASSIVE: |
| /* error passive state */ |
| priv->can.can_stats.error_passive++; |
| priv->can.state = CAN_STATE_ERROR_PASSIVE; |
| break; |
| case CAN_STATE_BUS_OFF: |
| /* bus-off state */ |
| priv->can.state = CAN_STATE_BUS_OFF; |
| ifi_canfd_irq_enable(ndev, 0); |
| priv->can.can_stats.bus_off++; |
| can_bus_off(ndev); |
| break; |
| default: |
| break; |
| } |
| |
| /* propagate the error condition to the CAN stack */ |
| skb = alloc_can_err_skb(ndev, &cf); |
| if (unlikely(!skb)) |
| return 0; |
| |
| ifi_canfd_get_berr_counter(ndev, &bec); |
| |
| switch (new_state) { |
| case CAN_STATE_ERROR_WARNING: |
| /* error warning state */ |
| cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT; |
| cf->data[1] = (bec.txerr > bec.rxerr) ? |
| CAN_ERR_CRTL_TX_WARNING : |
| CAN_ERR_CRTL_RX_WARNING; |
| cf->data[6] = bec.txerr; |
| cf->data[7] = bec.rxerr; |
| break; |
| case CAN_STATE_ERROR_PASSIVE: |
| /* error passive state */ |
| cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT; |
| cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE; |
| if (bec.txerr > 127) |
| cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE; |
| cf->data[6] = bec.txerr; |
| cf->data[7] = bec.rxerr; |
| break; |
| case CAN_STATE_BUS_OFF: |
| /* bus-off state */ |
| cf->can_id |= CAN_ERR_BUSOFF; |
| break; |
| default: |
| break; |
| } |
| |
| netif_receive_skb(skb); |
| |
| return 1; |
| } |
| |
| static int ifi_canfd_handle_state_errors(struct net_device *ndev) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| u32 stcmd = readl(priv->base + IFI_CANFD_STCMD); |
| int work_done = 0; |
| |
| if ((stcmd & IFI_CANFD_STCMD_ERROR_ACTIVE) && |
| (priv->can.state != CAN_STATE_ERROR_ACTIVE)) { |
| netdev_dbg(ndev, "Error, entered active state\n"); |
| work_done += ifi_canfd_handle_state_change(ndev, |
| CAN_STATE_ERROR_ACTIVE); |
| } |
| |
| if ((stcmd & IFI_CANFD_STCMD_ERROR_WARNING) && |
| (priv->can.state != CAN_STATE_ERROR_WARNING)) { |
| netdev_dbg(ndev, "Error, entered warning state\n"); |
| work_done += ifi_canfd_handle_state_change(ndev, |
| CAN_STATE_ERROR_WARNING); |
| } |
| |
| if ((stcmd & IFI_CANFD_STCMD_ERROR_PASSIVE) && |
| (priv->can.state != CAN_STATE_ERROR_PASSIVE)) { |
| netdev_dbg(ndev, "Error, entered passive state\n"); |
| work_done += ifi_canfd_handle_state_change(ndev, |
| CAN_STATE_ERROR_PASSIVE); |
| } |
| |
| if ((stcmd & IFI_CANFD_STCMD_BUSOFF) && |
| (priv->can.state != CAN_STATE_BUS_OFF)) { |
| netdev_dbg(ndev, "Error, entered bus-off state\n"); |
| work_done += ifi_canfd_handle_state_change(ndev, |
| CAN_STATE_BUS_OFF); |
| } |
| |
| return work_done; |
| } |
| |
| static int ifi_canfd_poll(struct napi_struct *napi, int quota) |
| { |
| struct net_device *ndev = napi->dev; |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| u32 rxstcmd = readl(priv->base + IFI_CANFD_RXSTCMD); |
| int work_done = 0; |
| |
| /* Handle bus state changes */ |
| work_done += ifi_canfd_handle_state_errors(ndev); |
| |
| /* Handle lost messages on RX */ |
| if (rxstcmd & IFI_CANFD_RXSTCMD_OVERFLOW) |
| work_done += ifi_canfd_handle_lost_msg(ndev); |
| |
| /* Handle lec errors on the bus */ |
| if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) |
| work_done += ifi_canfd_handle_lec_err(ndev); |
| |
| /* Handle normal messages on RX */ |
| if (!(rxstcmd & IFI_CANFD_RXSTCMD_EMPTY)) |
| work_done += ifi_canfd_do_rx_poll(ndev, quota - work_done); |
| |
| if (work_done < quota) { |
| napi_complete_done(napi, work_done); |
| ifi_canfd_irq_enable(ndev, 1); |
| } |
| |
| return work_done; |
| } |
| |
| static irqreturn_t ifi_canfd_isr(int irq, void *dev_id) |
| { |
| struct net_device *ndev = (struct net_device *)dev_id; |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| struct net_device_stats *stats = &ndev->stats; |
| const u32 rx_irq_mask = IFI_CANFD_INTERRUPT_RXFIFO_NEMPTY | |
| IFI_CANFD_INTERRUPT_RXFIFO_NEMPTY_PER | |
| IFI_CANFD_INTERRUPT_ERROR_COUNTER | |
| IFI_CANFD_INTERRUPT_ERROR_STATE_CHG | |
| IFI_CANFD_INTERRUPT_ERROR_WARNING | |
| IFI_CANFD_INTERRUPT_ERROR_BUSOFF; |
| const u32 tx_irq_mask = IFI_CANFD_INTERRUPT_TXFIFO_EMPTY | |
| IFI_CANFD_INTERRUPT_TXFIFO_REMOVE; |
| const u32 clr_irq_mask = ~((u32)IFI_CANFD_INTERRUPT_SET_IRQ); |
| u32 isr; |
| |
| isr = readl(priv->base + IFI_CANFD_INTERRUPT); |
| |
| /* No interrupt */ |
| if (isr == 0) |
| return IRQ_NONE; |
| |
| /* Clear all pending interrupts but ErrWarn */ |
| writel(clr_irq_mask, priv->base + IFI_CANFD_INTERRUPT); |
| |
| /* RX IRQ or bus warning, start NAPI */ |
| if (isr & rx_irq_mask) { |
| ifi_canfd_irq_enable(ndev, 0); |
| napi_schedule(&priv->napi); |
| } |
| |
| /* TX IRQ */ |
| if (isr & IFI_CANFD_INTERRUPT_TXFIFO_REMOVE) { |
| stats->tx_bytes += can_get_echo_skb(ndev, 0, NULL); |
| stats->tx_packets++; |
| } |
| |
| if (isr & tx_irq_mask) |
| netif_wake_queue(ndev); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static const struct can_bittiming_const ifi_canfd_bittiming_const = { |
| .name = KBUILD_MODNAME, |
| .tseg1_min = 1, /* Time segment 1 = prop_seg + phase_seg1 */ |
| .tseg1_max = 256, |
| .tseg2_min = 2, /* Time segment 2 = phase_seg2 */ |
| .tseg2_max = 256, |
| .sjw_max = 128, |
| .brp_min = 2, |
| .brp_max = 512, |
| .brp_inc = 1, |
| }; |
| |
| static void ifi_canfd_set_bittiming(struct net_device *ndev) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| const struct can_bittiming *bt = &priv->can.bittiming; |
| const struct can_bittiming *dbt = &priv->can.data_bittiming; |
| u16 brp, sjw, tseg1, tseg2, tdc; |
| |
| /* Configure bit timing */ |
| brp = bt->brp - 2; |
| sjw = bt->sjw - 1; |
| tseg1 = bt->prop_seg + bt->phase_seg1 - 1; |
| tseg2 = bt->phase_seg2 - 2; |
| writel((tseg2 << IFI_CANFD_TIME_TIMEB_OFF) | |
| (tseg1 << IFI_CANFD_TIME_TIMEA_OFF) | |
| (brp << IFI_CANFD_TIME_PRESCALE_OFF) | |
| (sjw << IFI_CANFD_TIME_SJW_OFF_7_9_8_8), |
| priv->base + IFI_CANFD_TIME); |
| |
| /* Configure data bit timing */ |
| brp = dbt->brp - 2; |
| sjw = dbt->sjw - 1; |
| tseg1 = dbt->prop_seg + dbt->phase_seg1 - 1; |
| tseg2 = dbt->phase_seg2 - 2; |
| writel((tseg2 << IFI_CANFD_TIME_TIMEB_OFF) | |
| (tseg1 << IFI_CANFD_TIME_TIMEA_OFF) | |
| (brp << IFI_CANFD_TIME_PRESCALE_OFF) | |
| (sjw << IFI_CANFD_TIME_SJW_OFF_7_9_8_8), |
| priv->base + IFI_CANFD_FTIME); |
| |
| /* Configure transmitter delay */ |
| tdc = dbt->brp * (dbt->prop_seg + dbt->phase_seg1); |
| tdc &= IFI_CANFD_TDELAY_MASK; |
| writel(IFI_CANFD_TDELAY_EN | tdc, priv->base + IFI_CANFD_TDELAY); |
| } |
| |
| static void ifi_canfd_set_filter(struct net_device *ndev, const u32 id, |
| const u32 mask, const u32 ident) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| |
| writel(mask, priv->base + IFI_CANFD_FILTER_MASK(id)); |
| writel(ident, priv->base + IFI_CANFD_FILTER_IDENT(id)); |
| } |
| |
| static void ifi_canfd_set_filters(struct net_device *ndev) |
| { |
| /* Receive all CAN frames (standard ID) */ |
| ifi_canfd_set_filter(ndev, 0, |
| IFI_CANFD_FILTER_MASK_VALID | |
| IFI_CANFD_FILTER_MASK_EXT, |
| IFI_CANFD_FILTER_IDENT_VALID); |
| |
| /* Receive all CAN frames (extended ID) */ |
| ifi_canfd_set_filter(ndev, 1, |
| IFI_CANFD_FILTER_MASK_VALID | |
| IFI_CANFD_FILTER_MASK_EXT, |
| IFI_CANFD_FILTER_IDENT_VALID | |
| IFI_CANFD_FILTER_IDENT_IDE); |
| |
| /* Receive all CANFD frames */ |
| ifi_canfd_set_filter(ndev, 2, |
| IFI_CANFD_FILTER_MASK_VALID | |
| IFI_CANFD_FILTER_MASK_EDL | |
| IFI_CANFD_FILTER_MASK_EXT, |
| IFI_CANFD_FILTER_IDENT_VALID | |
| IFI_CANFD_FILTER_IDENT_CANFD | |
| IFI_CANFD_FILTER_IDENT_IDE); |
| } |
| |
| static void ifi_canfd_start(struct net_device *ndev) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| u32 stcmd; |
| |
| /* Reset the IP */ |
| writel(IFI_CANFD_STCMD_HARDRESET, priv->base + IFI_CANFD_STCMD); |
| writel(IFI_CANFD_STCMD_ENABLE_7_9_8_8_TIMING, |
| priv->base + IFI_CANFD_STCMD); |
| |
| ifi_canfd_set_bittiming(ndev); |
| ifi_canfd_set_filters(ndev); |
| |
| /* Reset FIFOs */ |
| writel(IFI_CANFD_RXSTCMD_RESET, priv->base + IFI_CANFD_RXSTCMD); |
| writel(0, priv->base + IFI_CANFD_RXSTCMD); |
| writel(IFI_CANFD_TXSTCMD_RESET, priv->base + IFI_CANFD_TXSTCMD); |
| writel(0, priv->base + IFI_CANFD_TXSTCMD); |
| |
| /* Repeat transmission until successful */ |
| writel(0, priv->base + IFI_CANFD_REPEAT); |
| writel(0, priv->base + IFI_CANFD_SUSPEND); |
| |
| /* Clear all pending interrupts */ |
| writel((u32)(~IFI_CANFD_INTERRUPT_SET_IRQ), |
| priv->base + IFI_CANFD_INTERRUPT); |
| |
| stcmd = IFI_CANFD_STCMD_ENABLE | IFI_CANFD_STCMD_NORMAL_MODE | |
| IFI_CANFD_STCMD_ENABLE_7_9_8_8_TIMING; |
| |
| if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) |
| stcmd |= IFI_CANFD_STCMD_BUSMONITOR; |
| |
| if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) |
| stcmd |= IFI_CANFD_STCMD_LOOPBACK; |
| |
| if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) && |
| !(priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)) |
| stcmd |= IFI_CANFD_STCMD_ENABLE_ISO; |
| |
| if (!(priv->can.ctrlmode & CAN_CTRLMODE_FD)) |
| stcmd |= IFI_CANFD_STCMD_DISABLE_CANFD; |
| |
| priv->can.state = CAN_STATE_ERROR_ACTIVE; |
| |
| ifi_canfd_irq_enable(ndev, 1); |
| |
| /* Unlock, reset and enable the error counter. */ |
| writel(IFI_CANFD_ERROR_CTR_UNLOCK_MAGIC, |
| priv->base + IFI_CANFD_ERROR_CTR); |
| writel(IFI_CANFD_ERROR_CTR_ER_RESET, priv->base + IFI_CANFD_ERROR_CTR); |
| writel(IFI_CANFD_ERROR_CTR_ER_ENABLE, priv->base + IFI_CANFD_ERROR_CTR); |
| |
| /* Enable controller */ |
| writel(stcmd, priv->base + IFI_CANFD_STCMD); |
| } |
| |
| static void ifi_canfd_stop(struct net_device *ndev) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| |
| /* Reset and disable the error counter. */ |
| writel(IFI_CANFD_ERROR_CTR_ER_RESET, priv->base + IFI_CANFD_ERROR_CTR); |
| writel(0, priv->base + IFI_CANFD_ERROR_CTR); |
| |
| /* Reset the IP */ |
| writel(IFI_CANFD_STCMD_HARDRESET, priv->base + IFI_CANFD_STCMD); |
| |
| /* Mask all interrupts */ |
| writel(~0, priv->base + IFI_CANFD_IRQMASK); |
| |
| /* Clear all pending interrupts */ |
| writel((u32)(~IFI_CANFD_INTERRUPT_SET_IRQ), |
| priv->base + IFI_CANFD_INTERRUPT); |
| |
| /* Set the state as STOPPED */ |
| priv->can.state = CAN_STATE_STOPPED; |
| } |
| |
| static int ifi_canfd_set_mode(struct net_device *ndev, enum can_mode mode) |
| { |
| switch (mode) { |
| case CAN_MODE_START: |
| ifi_canfd_start(ndev); |
| netif_wake_queue(ndev); |
| break; |
| default: |
| return -EOPNOTSUPP; |
| } |
| |
| return 0; |
| } |
| |
| static int ifi_canfd_open(struct net_device *ndev) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| int ret; |
| |
| ret = open_candev(ndev); |
| if (ret) { |
| netdev_err(ndev, "Failed to open CAN device\n"); |
| return ret; |
| } |
| |
| /* Register interrupt handler */ |
| ret = request_irq(ndev->irq, ifi_canfd_isr, IRQF_SHARED, |
| ndev->name, ndev); |
| if (ret < 0) { |
| netdev_err(ndev, "Failed to request interrupt\n"); |
| goto err_irq; |
| } |
| |
| ifi_canfd_start(ndev); |
| |
| napi_enable(&priv->napi); |
| netif_start_queue(ndev); |
| |
| return 0; |
| err_irq: |
| close_candev(ndev); |
| return ret; |
| } |
| |
| static int ifi_canfd_close(struct net_device *ndev) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| |
| netif_stop_queue(ndev); |
| napi_disable(&priv->napi); |
| |
| ifi_canfd_stop(ndev); |
| |
| free_irq(ndev->irq, ndev); |
| |
| close_candev(ndev); |
| |
| return 0; |
| } |
| |
| static netdev_tx_t ifi_canfd_start_xmit(struct sk_buff *skb, |
| struct net_device *ndev) |
| { |
| struct ifi_canfd_priv *priv = netdev_priv(ndev); |
| struct canfd_frame *cf = (struct canfd_frame *)skb->data; |
| u32 txst, txid, txdlc; |
| int i; |
| |
| if (can_dev_dropped_skb(ndev, skb)) |
| return NETDEV_TX_OK; |
| |
| /* Check if the TX buffer is full */ |
| txst = readl(priv->base + IFI_CANFD_TXSTCMD); |
| if (txst & IFI_CANFD_TXSTCMD_FULL) { |
| netif_stop_queue(ndev); |
| netdev_err(ndev, "BUG! TX FIFO full when queue awake!\n"); |
| return NETDEV_TX_BUSY; |
| } |
| |
| netif_stop_queue(ndev); |
| |
| if (cf->can_id & CAN_EFF_FLAG) { |
| txid = cf->can_id & CAN_EFF_MASK; |
| /* |
| * In case the Extended ID frame is transmitted, the |
| * standard and extended part of the ID are swapped |
| * in the register, so swap them back to send the |
| * correct ID. |
| */ |
| txid = (txid >> IFI_CANFD_TXFIFO_ID_ID_XTD_WIDTH) | |
| ((txid & IFI_CANFD_TXFIFO_ID_ID_XTD_MASK) << |
| IFI_CANFD_TXFIFO_ID_ID_XTD_OFFSET); |
| txid |= IFI_CANFD_TXFIFO_ID_IDE; |
| } else { |
| txid = cf->can_id & CAN_SFF_MASK; |
| } |
| |
| txdlc = can_fd_len2dlc(cf->len); |
| if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) && can_is_canfd_skb(skb)) { |
| txdlc |= IFI_CANFD_TXFIFO_DLC_EDL; |
| if (cf->flags & CANFD_BRS) |
| txdlc |= IFI_CANFD_TXFIFO_DLC_BRS; |
| } |
| |
| if (cf->can_id & CAN_RTR_FLAG) |
| txdlc |= IFI_CANFD_TXFIFO_DLC_RTR; |
| |
| /* message ram configuration */ |
| writel(txid, priv->base + IFI_CANFD_TXFIFO_ID); |
| writel(txdlc, priv->base + IFI_CANFD_TXFIFO_DLC); |
| |
| for (i = 0; i < cf->len; i += 4) { |
| writel(*(u32 *)(cf->data + i), |
| priv->base + IFI_CANFD_TXFIFO_DATA + i); |
| } |
| |
| writel(0, priv->base + IFI_CANFD_TXFIFO_REPEATCOUNT); |
| writel(0, priv->base + IFI_CANFD_TXFIFO_SUSPEND_US); |
| |
| can_put_echo_skb(skb, ndev, 0, 0); |
| |
| /* Start the transmission */ |
| writel(IFI_CANFD_TXSTCMD_ADD_MSG, priv->base + IFI_CANFD_TXSTCMD); |
| |
| return NETDEV_TX_OK; |
| } |
| |
| static const struct net_device_ops ifi_canfd_netdev_ops = { |
| .ndo_open = ifi_canfd_open, |
| .ndo_stop = ifi_canfd_close, |
| .ndo_start_xmit = ifi_canfd_start_xmit, |
| .ndo_change_mtu = can_change_mtu, |
| }; |
| |
| static const struct ethtool_ops ifi_canfd_ethtool_ops = { |
| .get_ts_info = ethtool_op_get_ts_info, |
| }; |
| |
| static int ifi_canfd_plat_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct net_device *ndev; |
| struct ifi_canfd_priv *priv; |
| void __iomem *addr; |
| int irq, ret; |
| u32 id, rev; |
| |
| addr = devm_platform_ioremap_resource(pdev, 0); |
| if (IS_ERR(addr)) |
| return PTR_ERR(addr); |
| |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) |
| return -EINVAL; |
| |
| id = readl(addr + IFI_CANFD_IP_ID); |
| if (id != IFI_CANFD_IP_ID_VALUE) { |
| dev_err(dev, "This block is not IFI CANFD, id=%08x\n", id); |
| return -EINVAL; |
| } |
| |
| rev = readl(addr + IFI_CANFD_VER) & IFI_CANFD_VER_REV_MASK; |
| if (rev < IFI_CANFD_VER_REV_MIN_SUPPORTED) { |
| dev_err(dev, "This block is too old (rev %i), minimum supported is rev %i\n", |
| rev, IFI_CANFD_VER_REV_MIN_SUPPORTED); |
| return -EINVAL; |
| } |
| |
| ndev = alloc_candev(sizeof(*priv), 1); |
| if (!ndev) |
| return -ENOMEM; |
| |
| ndev->irq = irq; |
| ndev->flags |= IFF_ECHO; /* we support local echo */ |
| ndev->netdev_ops = &ifi_canfd_netdev_ops; |
| ndev->ethtool_ops = &ifi_canfd_ethtool_ops; |
| |
| priv = netdev_priv(ndev); |
| priv->ndev = ndev; |
| priv->base = addr; |
| |
| netif_napi_add(ndev, &priv->napi, ifi_canfd_poll); |
| |
| priv->can.state = CAN_STATE_STOPPED; |
| |
| priv->can.clock.freq = readl(addr + IFI_CANFD_CANCLOCK); |
| |
| priv->can.bittiming_const = &ifi_canfd_bittiming_const; |
| priv->can.data_bittiming_const = &ifi_canfd_bittiming_const; |
| priv->can.do_set_mode = ifi_canfd_set_mode; |
| priv->can.do_get_berr_counter = ifi_canfd_get_berr_counter; |
| |
| /* IFI CANFD can do both Bosch FD and ISO FD */ |
| priv->can.ctrlmode = CAN_CTRLMODE_FD; |
| |
| /* IFI CANFD can do both Bosch FD and ISO FD */ |
| priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | |
| CAN_CTRLMODE_LISTENONLY | |
| CAN_CTRLMODE_FD | |
| CAN_CTRLMODE_FD_NON_ISO | |
| CAN_CTRLMODE_BERR_REPORTING; |
| |
| platform_set_drvdata(pdev, ndev); |
| SET_NETDEV_DEV(ndev, dev); |
| |
| ret = register_candev(ndev); |
| if (ret) { |
| dev_err(dev, "Failed to register (ret=%d)\n", ret); |
| goto err_reg; |
| } |
| |
| dev_info(dev, "Driver registered: regs=%p, irq=%d, clock=%d\n", |
| priv->base, ndev->irq, priv->can.clock.freq); |
| |
| return 0; |
| |
| err_reg: |
| free_candev(ndev); |
| return ret; |
| } |
| |
| static void ifi_canfd_plat_remove(struct platform_device *pdev) |
| { |
| struct net_device *ndev = platform_get_drvdata(pdev); |
| |
| unregister_candev(ndev); |
| platform_set_drvdata(pdev, NULL); |
| free_candev(ndev); |
| } |
| |
| static const struct of_device_id ifi_canfd_of_table[] = { |
| { .compatible = "ifi,canfd-1.0", .data = NULL }, |
| { /* sentinel */ }, |
| }; |
| MODULE_DEVICE_TABLE(of, ifi_canfd_of_table); |
| |
| static struct platform_driver ifi_canfd_plat_driver = { |
| .driver = { |
| .name = KBUILD_MODNAME, |
| .of_match_table = ifi_canfd_of_table, |
| }, |
| .probe = ifi_canfd_plat_probe, |
| .remove = ifi_canfd_plat_remove, |
| }; |
| |
| module_platform_driver(ifi_canfd_plat_driver); |
| |
| MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("CAN bus driver for IFI CANFD controller"); |