// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 Rockchip Electronics Co. Ltd. * Rockchip CAN driver */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define DRV_NAME "rockchip_can" #define CAN_MODE 0x00 #define RESET_MODE 0 #define WORK_MODE BIT(0) #define SELF_TEST_EN BIT(2) #define MODE_AUTO_RETX BIT(10) #define CAN_CMD 0x04 #define TX_REQ BIT(0) #define CAN_STATE 0x08 #define RX_BUF_FULL BIT(0) #define TX_BUF_FULL BIT(1) #define RX_PERIOD BIT(2) #define TX_PERIOD BIT(3) #define ERR_WARN BIT(4) #define BUS_OFF BIT(5) #define CAN_INT 0x0C #define CAN_INT_MASK 0x10 #define RX_FINISH BIT(0) #define TX_FINISH BIT(1) #define ERR_WARN_INT BIT(2) #define RX_BUF_OV BIT(3) #define PASSIVE_ERR BIT(4) #define TX_LOSTARB BIT(5) #define BUS_ERR_INT BIT(6) /* Bit Timing Register */ #define CAN_BTT 0x18 #define MODE_3_SAMPLES BIT(16) #define BT_SJW_SHIFT 14 #define BT_SJW_MASK GENMASK(15, 14) #define BT_BRP_SHIFT 8 #define BT_BRP_MASK GENMASK(13, 8) #define BT_TSEG2_SHIFT 4 #define BT_TSEG2_MASK GENMASK(6, 4) #define BT_TSEG1_SHIFT 0 #define BT_TSEG1_MASK GENMASK(3, 0) #define CAN_LOSTARB_CODE 0x28 #define CAN_ERR_CODE 0x2c #define ERR_TYPE_MASK GENMASK(24, 22) #define ERR_TYPE_SHIFT 22 #define BIT_ERR 0 #define STUFF_ERR 1 #define FORM_ERR 2 #define ACK_ERR 3 #define CRC_ERR 4 #define ERR_DIR_RX BIT(21) #define ERR_LOC_MASK GENMASK(13, 0) #define CAN_RX_ERR_CNT 0x34 #define CAN_TX_ERR_CNT 0x38 #define CAN_ID 0x3c #define CAN_ID_MASK 0x40 #define CAN_TX_FRM_INFO 0x50 #define CAN_EFF BIT(7) #define CAN_RTR BIT(6) #define CAN_DLC_MASK GENMASK(3, 0) #define CAN_DLC(x) ((x) & GENMASK(3, 0)) #define CAN_TX_ID 0x54 #define CAN_TX_ID_MASK 0x1fffffff #define CAN_TX_DATA1 0x58 #define CAN_TX_DATA2 0x5c #define CAN_RX_FRM_INFO 0x60 #define CAN_RX_ID 0x64 #define CAN_RX_DATA1 0x68 #define CAN_RX_DATA2 0x6c #define CAN_RX_FILTER_MASK 0x1fffffff #define CAN_VERSION 0x70 struct rockchip_can { struct can_priv can; void __iomem *base; struct device *dev; struct clk_bulk_data *clks; int num_clks; struct reset_control *reset; }; static const struct can_bittiming_const rockchip_can_bittiming_const = { .name = DRV_NAME, .tseg1_min = 1, .tseg1_max = 16, .tseg2_min = 1, .tseg2_max = 8, .sjw_max = 4, .brp_min = 1, .brp_max = 128, .brp_inc = 2, }; static void rockchip_can_write_cmdreg(struct rockchip_can *rcan, u8 val) { writel(val, rcan->base + CAN_CMD); } static int set_reset_mode(struct net_device *ndev) { struct rockchip_can *rcan = netdev_priv(ndev); reset_control_assert(rcan->reset); udelay(2); reset_control_deassert(rcan->reset); writel(0, rcan->base + CAN_MODE); return 0; } static int set_normal_mode(struct net_device *ndev) { struct rockchip_can *rcan = netdev_priv(ndev); u32 val; val = readl(rcan->base + CAN_MODE); val |= WORK_MODE | MODE_AUTO_RETX; writel(val, rcan->base + CAN_MODE); return 0; } /* bittiming is called in reset_mode only */ static int rockchip_can_set_bittiming(struct net_device *ndev) { struct rockchip_can *rcan = netdev_priv(ndev); struct can_bittiming *bt = &rcan->can.bittiming; u32 cfg; cfg = ((bt->sjw - 1) << BT_SJW_SHIFT) | (((bt->brp >> 1) - 1) << BT_BRP_SHIFT) | ((bt->phase_seg2 - 1) << BT_TSEG2_SHIFT) | ((bt->prop_seg + bt->phase_seg1 - 1)); if (rcan->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES) cfg |= MODE_3_SAMPLES; writel(cfg, rcan->base + CAN_BTT); netdev_dbg(ndev, "setting BITTIMING=0x%08x brp: %d bitrate:%d\n", cfg, bt->brp, bt->bitrate); return 0; } static int rockchip_can_get_berr_counter(const struct net_device *ndev, struct can_berr_counter *bec) { struct rockchip_can *rcan = netdev_priv(ndev); int err; err = pm_runtime_get_sync(rcan->dev); if (err < 0) { netdev_err(ndev, "%s: pm_runtime_get failed(%d)\n", __func__, err); return err; } bec->rxerr = readl(rcan->base + CAN_RX_ERR_CNT); bec->txerr = readl(rcan->base + CAN_TX_ERR_CNT); pm_runtime_put(rcan->dev); netdev_dbg(ndev, "%s\n", __func__); return 0; } static int rockchip_can_start(struct net_device *ndev) { struct rockchip_can *rcan = netdev_priv(ndev); /* we need to enter the reset mode */ set_reset_mode(ndev); writel(0, rcan->base + CAN_INT_MASK); /* RECEIVING FILTER, accept all */ writel(0, rcan->base + CAN_ID); writel(CAN_RX_FILTER_MASK, rcan->base + CAN_ID_MASK); rockchip_can_set_bittiming(ndev); set_normal_mode(ndev); rcan->can.state = CAN_STATE_ERROR_ACTIVE; netdev_dbg(ndev, "%s\n", __func__); return 0; } static int rockchip_can_stop(struct net_device *ndev) { struct rockchip_can *rcan = netdev_priv(ndev); u32 val; rcan->can.state = CAN_STATE_STOPPED; /* we need to enter reset mode */ set_reset_mode(ndev); /* disable all interrupts */ val = RX_FINISH | TX_FINISH | ERR_WARN_INT | RX_BUF_OV | PASSIVE_ERR | TX_LOSTARB | BUS_ERR_INT; writel(val, rcan->base + CAN_INT_MASK); netdev_dbg(ndev, "%s\n", __func__); return 0; } static int rockchip_can_set_mode(struct net_device *ndev, enum can_mode mode) { int err; netdev_dbg(ndev, "can set mode: 0x%x\n", mode); switch (mode) { case CAN_MODE_START: err = rockchip_can_start(ndev); if (err) { netdev_err(ndev, "starting CAN controller failed!\n"); return err; } if (netif_queue_stopped(ndev)) netif_wake_queue(ndev); break; default: return -EOPNOTSUPP; } return 0; } /* transmit a CAN message * message layout in the sk_buff should be like this: * xx xx xx xx ff ll 00 11 22 33 44 55 66 77 * [ can_id ] [flags] [len] [can data (up to 8 bytes] */ static int rockchip_can_start_xmit(struct sk_buff *skb, struct net_device *ndev) { struct rockchip_can *rcan = netdev_priv(ndev); struct can_frame *cf = (struct can_frame *)skb->data; canid_t id; u8 dlc; u32 fi; u32 data1 = 0, data2 = 0; if (can_dropped_invalid_skb(ndev, skb)) return NETDEV_TX_OK; netif_stop_queue(ndev); id = cf->can_id; dlc = cf->can_dlc; fi = dlc; if (id & CAN_RTR_FLAG) { fi |= CAN_RTR; fi &= ~CAN_DLC_MASK; } if (id & CAN_EFF_FLAG) fi |= CAN_EFF; rockchip_can_write_cmdreg(rcan, 0); writel(id & CAN_TX_ID_MASK, rcan->base + CAN_TX_ID); if (!(id & CAN_RTR_FLAG)) { data1 = le32_to_cpup((__le32 *)&cf->data[0]); data2 = le32_to_cpup((__le32 *)&cf->data[4]); writel(data1, rcan->base + CAN_TX_DATA1); writel(data2, rcan->base + CAN_TX_DATA2); } writel(fi, rcan->base + CAN_TX_FRM_INFO); can_put_echo_skb(skb, ndev, 0); rockchip_can_write_cmdreg(rcan, TX_REQ); netdev_dbg(ndev, "TX: can_id:0x%08x dlc: %d mode: 0x%08x data: 0x%08x 0x%08x\n", cf->can_id, cf->can_dlc, rcan->can.ctrlmode, data1, data2); return NETDEV_TX_OK; } static void rockchip_can_rx(struct net_device *ndev) { struct rockchip_can *rcan = netdev_priv(ndev); struct net_device_stats *stats = &ndev->stats; struct can_frame *cf; struct sk_buff *skb; canid_t id; u8 fi; u32 data1 = 0, data2 = 0; /* create zero'ed CAN frame buffer */ skb = alloc_can_skb(ndev, &cf); if (!skb) return; fi = readl(rcan->base + CAN_RX_FRM_INFO); cf->can_dlc = get_can_dlc(fi & CAN_DLC_MASK); id = readl(rcan->base + CAN_RX_ID); if (fi & CAN_EFF) id |= CAN_EFF_FLAG; /* remote frame ? */ if (fi & CAN_RTR) { id |= CAN_RTR_FLAG; } else { data1 = readl(rcan->base + CAN_RX_DATA1); data2 = readl(rcan->base + CAN_RX_DATA2); } cf->can_id = id; *(__le32 *)(cf->data + 0) = cpu_to_le32(data1); *(__le32 *)(cf->data + 4) = cpu_to_le32(data2); stats->rx_packets++; stats->rx_bytes += cf->can_dlc; netif_rx(skb); can_led_event(ndev, CAN_LED_EVENT_RX); netdev_dbg(ndev, "%s can_id:0x%08x fi: 0x%08x dlc: %d data: 0x%08x 0x%08x\n", __func__, cf->can_id, fi, cf->can_dlc, data1, data2); } static void rockchip_can_clean_rx_info(struct rockchip_can *rcan) { readl(rcan->base + CAN_RX_FRM_INFO); readl(rcan->base + CAN_RX_ID); readl(rcan->base + CAN_RX_DATA1); readl(rcan->base + CAN_RX_DATA2); } static int rockchip_can_err(struct net_device *ndev, u8 isr) { struct rockchip_can *rcan = netdev_priv(ndev); struct net_device_stats *stats = &ndev->stats; struct can_frame *cf; struct sk_buff *skb; unsigned int rxerr, txerr; u32 sta_reg; skb = alloc_can_err_skb(ndev, &cf); rxerr = readl(rcan->base + CAN_RX_ERR_CNT); txerr = readl(rcan->base + CAN_TX_ERR_CNT); sta_reg = readl(rcan->base + CAN_STATE); // printk("rxerr = %x txerr=%x sta_reg= %x isr= %x \n", rxerr, txerr, sta_reg, isr); if (sta_reg & BUS_OFF) { rcan->can.state = CAN_STATE_BUS_OFF; //rcan->can.can_stats.bus_off++; //cf->can_id |= CAN_ERR_BUSOFF; } else if (sta_reg & ERR_WARN) { rcan->can.can_stats.error_warning++; rcan->can.state = CAN_STATE_ERROR_WARNING; /* error warning state */ if (likely(skb)) { cf->can_id |= CAN_ERR_CRTL; cf->data[1] = (txerr > rxerr) ? CAN_ERR_CRTL_TX_WARNING : CAN_ERR_CRTL_RX_WARNING; cf->data[6] = txerr; cf->data[7] = rxerr; } } else if (isr & PASSIVE_ERR) { rcan->can.can_stats.error_passive++; rcan->can.state = CAN_STATE_ERROR_PASSIVE; /* error passive state */ cf->can_id |= CAN_ERR_CRTL; cf->data[1] = (txerr > rxerr) ? CAN_ERR_CRTL_TX_WARNING : CAN_ERR_CRTL_RX_WARNING; cf->data[6] = txerr; cf->data[7] = rxerr; } if ((rcan->can.state != CAN_STATE_ERROR_WARNING)) { netif_stop_queue(ndev); rockchip_can_stop(ndev); can_free_echo_skb(ndev, 0); rockchip_can_start(ndev); netif_start_queue(ndev); //can_bus_off(ndev); } stats->rx_packets++; stats->rx_bytes += cf->can_dlc; netif_receive_skb(skb); return 0; } static irqreturn_t rockchip_can_interrupt(int irq, void *dev_id) { struct net_device *ndev = (struct net_device *)dev_id; struct rockchip_can *rcan = netdev_priv(ndev); struct net_device_stats *stats = &ndev->stats; u8 err_int = ERR_WARN_INT | RX_BUF_OV | PASSIVE_ERR | TX_LOSTARB | BUS_ERR_INT; u8 isr; isr = readl(rcan->base + CAN_INT); if (isr & TX_FINISH) { /* transmission complete interrupt */ stats->tx_bytes += readl(rcan->base + CAN_TX_FRM_INFO) & CAN_DLC_MASK; stats->tx_packets++; rockchip_can_write_cmdreg(rcan, 0); can_get_echo_skb(ndev, 0); netif_wake_queue(ndev); can_led_event(ndev, CAN_LED_EVENT_TX); } if (isr & RX_FINISH) rockchip_can_rx(ndev); if (isr & err_int) { rockchip_can_clean_rx_info(rcan); if (rockchip_can_err(ndev, isr)) netdev_err(ndev, "can't allocate buffer - clearing pending interrupts\n"); } writel(isr, rcan->base + CAN_INT); rockchip_can_clean_rx_info(rcan); netdev_dbg(ndev, "isr: 0x%x\n", isr); return IRQ_HANDLED; } static int rockchip_can_open(struct net_device *ndev) { struct rockchip_can *rcan = netdev_priv(ndev); int err; /* common open */ err = open_candev(ndev); if (err) return err; err = pm_runtime_get_sync(rcan->dev); if (err < 0) { netdev_err(ndev, "%s: pm_runtime_get failed(%d)\n", __func__, err); goto exit; } err = rockchip_can_start(ndev); if (err) { netdev_err(ndev, "could not start CAN peripheral\n"); goto exit_can_start; } can_led_event(ndev, CAN_LED_EVENT_OPEN); netif_start_queue(ndev); netdev_dbg(ndev, "%s\n", __func__); return 0; exit_can_start: pm_runtime_put(rcan->dev); exit: close_candev(ndev); return err; } static int rockchip_can_close(struct net_device *ndev) { struct rockchip_can *rcan = netdev_priv(ndev); netif_stop_queue(ndev); rockchip_can_stop(ndev); close_candev(ndev); can_led_event(ndev, CAN_LED_EVENT_STOP); pm_runtime_put(rcan->dev); netdev_dbg(ndev, "%s\n", __func__); return 0; } static const struct net_device_ops rockchip_can_netdev_ops = { .ndo_open = rockchip_can_open, .ndo_stop = rockchip_can_close, .ndo_start_xmit = rockchip_can_start_xmit, }; /** * rockchip_can_suspend - Suspend method for the driver * @dev: Address of the device structure * * Put the driver into low power mode. * Return: 0 on success and failure value on error */ static int __maybe_unused rockchip_can_suspend(struct device *dev) { struct net_device *ndev = dev_get_drvdata(dev); if (netif_running(ndev)) { netif_stop_queue(ndev); netif_device_detach(ndev); rockchip_can_stop(ndev); } return pm_runtime_force_suspend(dev); } /** * rockchip_can_resume - Resume from suspend * @dev: Address of the device structure * * Resume operation after suspend. * Return: 0 on success and failure value on error */ static int __maybe_unused rockchip_can_resume(struct device *dev) { struct net_device *ndev = dev_get_drvdata(dev); int ret; ret = pm_runtime_force_resume(dev); if (ret) { dev_err(dev, "pm_runtime_force_resume failed on resume\n"); return ret; } if (netif_running(ndev)) { ret = rockchip_can_start(ndev); if (ret) { dev_err(dev, "rockchip_can_chip_start failed on resume\n"); return ret; } netif_device_attach(ndev); netif_start_queue(ndev); } return 0; } /** * rockchip_can_runtime_suspend - Runtime suspend method for the driver * @dev: Address of the device structure * * Put the driver into low power mode. * Return: 0 always */ static int __maybe_unused rockchip_can_runtime_suspend(struct device *dev) { struct net_device *ndev = dev_get_drvdata(dev); struct rockchip_can *rcan = netdev_priv(ndev); clk_bulk_disable_unprepare(rcan->num_clks, rcan->clks); return 0; } /** * rockchip_can_runtime_resume - Runtime resume from suspend * @dev: Address of the device structure * * Resume operation after suspend. * Return: 0 on success and failure value on error */ static int __maybe_unused rockchip_can_runtime_resume(struct device *dev) { struct net_device *ndev = dev_get_drvdata(dev); struct rockchip_can *rcan = netdev_priv(ndev); int ret; ret = clk_bulk_prepare_enable(rcan->num_clks, rcan->clks); if (ret) { dev_err(dev, "Cannot enable clock.\n"); return ret; } return 0; } static const struct dev_pm_ops rockchip_can_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(rockchip_can_suspend, rockchip_can_resume) SET_RUNTIME_PM_OPS(rockchip_can_runtime_suspend, rockchip_can_runtime_resume, NULL) }; static const struct of_device_id rockchip_can_of_match[] = { {.compatible = "rockchip,can-1.0"}, {}, }; MODULE_DEVICE_TABLE(of, rockchip_can_of_match); static int rockchip_can_probe(struct platform_device *pdev) { struct net_device *ndev; struct rockchip_can *rcan; struct resource *res; void __iomem *addr; int err, irq; irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "could not get a valid irq\n"); return -ENODEV; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); addr = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(addr)) return -EBUSY; ndev = alloc_candev(sizeof(struct rockchip_can), 1); if (!ndev) { dev_err(&pdev->dev, "could not allocate memory for CAN device\n"); return -ENOMEM; } ndev->netdev_ops = &rockchip_can_netdev_ops; ndev->irq = irq; ndev->flags |= IFF_ECHO; rcan = netdev_priv(ndev); /* register interrupt handler */ err = devm_request_irq(&pdev->dev, ndev->irq, rockchip_can_interrupt, 0, ndev->name, ndev); if (err) { dev_err(&pdev->dev, "request_irq err: %d\n", err); return err; } rcan->reset = devm_reset_control_array_get(&pdev->dev, false, false); if (IS_ERR(rcan->reset)) { if (PTR_ERR(rcan->reset) != -EPROBE_DEFER) dev_err(&pdev->dev, "failed to get rcan reset lines\n"); return PTR_ERR(rcan->reset); } rcan->num_clks = devm_clk_bulk_get_all(&pdev->dev, &rcan->clks); if (rcan->num_clks < 1) { dev_err(&pdev->dev, "bus clock not found\n"); return -ENODEV; } rcan->dev = &pdev->dev; rcan->can.clock.freq = clk_get_rate(rcan->clks[0].clk); rcan->can.bittiming_const = &rockchip_can_bittiming_const; rcan->can.do_set_mode = rockchip_can_set_mode; rcan->can.do_get_berr_counter = rockchip_can_get_berr_counter; rcan->can.ctrlmode_supported = CAN_CTRLMODE_BERR_REPORTING | CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_3_SAMPLES; rcan->base = addr; platform_set_drvdata(pdev, ndev); SET_NETDEV_DEV(ndev, &pdev->dev); pm_runtime_enable(&pdev->dev); err = pm_runtime_get_sync(&pdev->dev); if (err < 0) { dev_err(&pdev->dev, "%s: pm_runtime_get failed(%d)\n", __func__, err); goto err_pmdisable; } err = register_candev(ndev); if (err) { dev_err(&pdev->dev, "registering %s failed (err=%d)\n", DRV_NAME, err); goto err_disableclks; } devm_can_led_init(ndev); pm_runtime_put(&pdev->dev); return 0; err_disableclks: pm_runtime_put(&pdev->dev); err_pmdisable: pm_runtime_disable(&pdev->dev); free_candev(ndev); return err; } static int rockchip_can_remove(struct platform_device *pdev) { struct net_device *ndev = platform_get_drvdata(pdev); unregister_netdev(ndev); pm_runtime_disable(&pdev->dev); free_candev(ndev); return 0; } static struct platform_driver rockchip_can_driver = { .driver = { .name = DRV_NAME, .pm = &rockchip_can_dev_pm_ops, .of_match_table = rockchip_can_of_match, }, .probe = rockchip_can_probe, .remove = rockchip_can_remove, }; module_platform_driver(rockchip_can_driver); MODULE_AUTHOR("Andy Yan "); MODULE_DESCRIPTION("Rockchip CAN Drivers");