SUNIX SDC CAN controller driver
PATCH 1/1 SUNIX SDC CAN controller driver
From: Morris Ku <[email protected]>
Add support for SUNIX SDC CAN controller
Cc Jason Lee <[email protected]>
Cc Taian Chen <[email protected]>
Cc Morris Ku <[email protected]>
Cc Edward Lee <[email protected]>
Signed-off-by Morris Ku <[email protected]>
--- sx2010_can.c | 1243 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1243 insertions(+) create mode 100644 sx2010_can.c
sx2010_can.c new file +1243 -0
@@ -0,0 +1,1243 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* SUNIX SDC CAN controller driver. +* +* Copyright (C) 2021, SUNIX Co., Ltd. +* +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/of_platform.h> +#include <linux/io.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/can/core.h> +#include <linux/can/dev.h> +#include <linux/can/led.h> + +#define DRIVER_NAME "sx2010_can" + +#define SX2010_REG_CTRL 0 +#define SX2010_CTRL_ENABLE BIT(0) +#define SX2010_CTRL_TX_IRQ_ENABLE BIT(1) +#define SX2010_CTRL_RX_IRQ_ENABLE BIT(2) +#define SX2010_CTRL_DEV_IRQ_ENABLE BIT(3) +#define SX2010_REG_STATUS 4 +#define SX2010_STATUS_BUSY BIT(0) +#define SX2010_STATUS_TX_IRQ BIT(1) +#define SX2010_STATUS_RX_IRQ BIT(2) +#define SX2010_STATUS_DEV_IRQ BIT(3) +#define SX2010_REG_TIMING_CTRL 8 +#define SX2010_REG_IRQ_ENABLE 12 +#define SX2010_REG_IRQ_STATUS 16 +#define SX2010_REG_GPIO_OUT_ENABLE 20 +#define SX2010_REG_GPIO_OUT 24 +#define SX2010_REG_TRANS_CTRL_0 28 +#define SX2010_TRANS_ENABLE BIT(0) +#define SX2010_TRANS_WRITE BIT(1) +#define SX2010_REG_TRANS_CTRL_1 32 +#define SX2010_REG_RAM 512 + +#define SX2010_WAIT_MAX_LOOP 2000 +#define SX2010_ISR_MAX_CNT 10 +#define SX2010_ISR_TIME (HZ / 100) +#define SX2010_CAN_MAX_DATA_LEN 8 +#define SX2010_CAN_TRANS_BUF_LEN (6 + SX2010_CAN_MAX_DATA_LEN) +#define SX2010_BUF_LEN 512 +#define SX2010_TX_ECHO_SKB_MAX 1 +#define SX2010_OST_DELAY_MS (5) +#define SX2010_FREQUENCY 20000000 + +#define SX2010_CMD_WRITE 0x02 +#define SX2010_CMD_READ 0x03 +#define SX2010_CMD_BIT_MODIFY 0x05 +#define SX2010_CMD_WRITE_TXB (n) (0x40 + 2 * (n)) +#define SX2010_CMD_READ_RXB (n) (((n) == 0) ? 0x90 : 0x94) +#define SX2010_CMD_RESET 0xC0 +#define SX2010_CMD_RTS (n) (0x80 | ((n) & 0x07)) + +#define REG_STAT 0x0e +#define REG_CTRL 0x0f +#define REG_CTRL_MASK GENMASK(7, 5) +#define REG_CTRL_CONF BIT(7) +#define REG_CTRL_LISTEN_ONLY GENMASK(6, 5) +#define REG_CTRL_LOOPBACK BIT(6) +#define REG_CTRL_SLEEP BIT(5) +#define REG_CTRL_NORMAL 0 +#define REG_CFG1 0x2a +#define REG_CFG2 0x29 +#define REG_CFG2_BTLMODE BIT(7) +#define REG_CFG2_SAM BIT(6) +#define REG_CFG3 0x28 +#define REG_CFG3_PHSEG2_MASK GENMASK(2, 0) +#define REG_INTE 0x2b +#define REG_INTE_ALL GENMASK(5, 0) +#define REG_INTF 0x2c +#define REG_INTF_RX1 BIT(1) +#define REG_INTF_RX0 BIT(0) +#define REG_INTF_RX GENMASK(1, 0) +#define REG_INTF_TX GENMASK(4, 2) +#define REG_INTF_ERR BIT(5) +#define REG_EFLG 0x2d +#define REG_EFLG_RXWAR BIT(1) +#define REG_EFLG_TXWAR BIT(2) +#define REG_EFLG_RXEP BIT(3) +#define REG_EFLG_TXEP BIT(4) +#define REG_EFLG_TXBO BIT(5) +#define REG_EFLG_RX0OVR BIT(6) +#define REG_EFLG_RX1OVR BIT(7) +#define REG_TXBCTRL (n) (((n) * 0x10) + 0x30) +#define REG_RXBCTRL (n) (((n) * 0x10) + 0x60) +#define REG_RXBCTRL_BUKT BIT(2) +#define REG_RXBCTRL_RXM0 BIT(5) +#define REG_RXBCTRL_RXM1 BIT(6) +#define REG_RXBSIDL_IDE BIT(3) +#define REG_RXBSIDL_SRR BIT(4) +#define REG_RXBSIDL_EID GENMASK(1, 0) +#define REG_RXBDLC_LEN_MASK GENMASK(3, 0) +#define REG_RXBDLC_RTR BIT(6) + +#define GET_BYTE (val, byte) \ + (((val) >> ((byte) * 8)) & 0xff) +#define SET_BYTE (val, byte) \ + (((val) & 0xff) << ((byte) * 8)) + +static const struct can_bittiming_const sx2010_bittiming_const = { + .name =DRIVER_NAME , + .tseg1_min =3 , + .tseg1_max =16 , + .tseg2_min =2 , + .tseg2_max =8 , + .sjw_max =4 , + .brp_min =1 , + .brp_max =64 , + .brp_inc =1 , +}; + +struct sx2010_can_data { +u32 board_id; +u8 chl_number; +u8 version; +u32 significand; +u8 exponent; +u8 gpio_input; +u8 gpio_output; +}; + +struct sx2010_data { +struct can_priv can; +struct net_device *net; +struct mutex trans_lock; +struct mutex sx_lock; +u8 *tx_buf; +u8 *rx_buf; +struct sk_buff *tx_skb; +int tx_len; +struct workqueue_struct *wq; +struct work_struct tx_work; +struct work_struct restart_work; + +int force_quit; +int after_suspend; +#define SX2010_AFTER_SUSPEND_UP 1 +#define SX2010_AFTER_SUSPEND_DOWN 2 +#define SX2010_AFTER_SUSPEND_POWER 4 +#define SX2010_AFTER_SUSPEND_RESTART 8 +int restart_tx; + +struct sx2010_can_data data; +struct device *dev; +struct resource *res; +u8 chip_select; +unsigned long long clk_rate; +struct timer_list timer; +void __iomem *base; +}; + +static inline u32 sx2010_readl (struct sx2010_data *data,u32 reg) +{ +return readl (data->base + reg); +} + +static inline void sx2010_writel (struct sx2010_data *data,u32 reg, +u32 val) +{ +writel (val, data->base + reg); +} + +static inline int sx2010_wait_till_ready (struct sx2010_data *data, +bool wait_rx_irq) +{ +bool available = false; +bool rx_irq = false; +u32 status; +int i; + +for (i =0 ; i <SX2010_WAIT_MAX_LOOP ; i++) { + status =sx2010_readl (data,SX2010_REG_STATUS ); +if (!(status &SX2010_STATUS_BUSY )) + available = true; +if (status &SX2010_STATUS_RX_IRQ ) + rx_irq = true; + +if (wait_rx_irq) { +if (available && rx_irq) +return 0 ; + }else { +if (available) +return 0 ; + } +cpu_relax (); +udelay (1 ); + } + +return -ETIMEDOUT ; +} + +static int sx2010_write_trans (struct sx2010_data *data,int cmd_len, +int data_len) +{ +int len_in_dw; +int total_len; +u32 val; +int ret; +int i; +int j; + +if (cmd_len <=0 ) +return -EINVAL ; + + total_len = cmd_len + data_len; +mutex_lock (&data->trans_lock); + + ret =sx2010_wait_till_ready (data, false); +if (ret) + goto out_unlock; + +/* Set ram */ +if ((total_len %4 ) ==0 ) { + len_in_dw = total_len /4 ; + }else { +if (total_len <4 ) + len_in_dw =1 ; +else + len_in_dw = (total_len /4 ) +1 ; + } + + j =0 ; +for (i =0 ; i < len_in_dw; i++) { +do { + val = data->tx_buf[i *4 ]; +if (++j == total_len) +break ; + val |= data->tx_buf[i *4 +1 ] <<8 ; +if (++j == total_len) +break ; + val |= data->tx_buf[i *4 +2 ] <<16 ; +if (++j == total_len) +break ; + val |= data->tx_buf[i *4 +3 ] <<24 ; +if (++j == total_len) +break ; + }while (false); + +sx2010_writel (data,SX2010_REG_RAM + (i *4 ), val); + } + +/* Set trans reg 1 */ + val = data_len <<16 ; +sx2010_writel (data,SX2010_REG_TRANS_CTRL_1 , val); + +/* Set trans reg 0 */ + val =SX2010_TRANS_ENABLE |SX2010_TRANS_WRITE ; + val |= cmd_len <<8 ; + val |= data->chip_select <<16 ; +sx2010_writel (data,SX2010_REG_TRANS_CTRL_0 , val); + + ret =sx2010_wait_till_ready (data, false); + +out_unlock: +mutex_unlock (&data->trans_lock); +return ret; +} + +static int sx2010_read_trans (struct sx2010_data *data,int cmd_len, +int data_len) +{ +int len_in_dw; +u32 val; +int ret; +int i; +int j; + +if (cmd_len <=0 ) +return -EINVAL ; + +mutex_lock (&data->trans_lock); + + ret =sx2010_wait_till_ready (data, false); +if (ret) + goto out_unlock; + +/* Set ram */ +if ((cmd_len %4 ) ==0 ) { + len_in_dw = cmd_len /4 ; + }else { +if (cmd_len <4 ) + len_in_dw =1 ; +else + len_in_dw = (cmd_len /4 ) +1 ; + } + + j =0 ; +for (i =0 ; i < len_in_dw; i++) { +do { + val = data->tx_buf[i *4 ]; +if (++j == cmd_len) +break ; + val |= data->tx_buf[i *4 +1 ] <<8 ; +if (++j == cmd_len) +break ; + val |= data->tx_buf[i *4 +2 ] <<16 ; +if (++j == cmd_len) +break ; + val |= data->tx_buf[i *4 +3 ] <<24 ; +if (++j == cmd_len) +break ; + }while (false); + +sx2010_writel (data,SX2010_REG_RAM + (i *4 ), val); + } + +/* Set trans reg 1 */ + val = data_len; +sx2010_writel (data,SX2010_REG_TRANS_CTRL_1 , val); + +/* Set trans reg 0 */ + val =SX2010_TRANS_ENABLE ; + val |= cmd_len <<8 ; + val |= data->chip_select <<16 ; +sx2010_writel (data,SX2010_REG_TRANS_CTRL_0 , val); + + ret =sx2010_wait_till_ready (data, true); +if (ret) + goto out_unlock; + +/* Get ram */ +if ((data_len %4 ) ==0 ) { + len_in_dw = data_len /4 ; + }else { +if (data_len <4 ) + len_in_dw =1 ; +else + len_in_dw = (data_len /4 ) +1 ; + } + + j =0 ; +for (i =0 ; i < len_in_dw; i++) { + val =sx2010_readl (data,SX2010_REG_RAM + (i *4 )); + data->rx_buf[j++] = val &GENMASK (7 ,0 ); +if (j == data_len) +break ; + data->rx_buf[j++] = (val &GENMASK (15 ,8 )) >>8 ; +if (j == data_len) +break ; + data->rx_buf[j++] = (val &GENMASK (23 ,16 )) >>16 ; +if (j == data_len) +break ; + data->rx_buf[j++] = (val &GENMASK (31 ,24 )) >>24 ; +if (j == data_len) +break ; + } + +out_unlock: +mutex_unlock (&data->trans_lock); +return ret; +} + +static void sx2010_clean (struct net_device *net) +{ +struct sx2010_data *data =netdev_priv (net); + +if (data->tx_skb || data->tx_len) + net->stats.tx_errors++; +dev_kfree_skb (data->tx_skb); +if (data->tx_len) +can_free_echo_skb (data->net,0 ); + data->tx_skb =NULL ; + data->tx_len =0 ; +} + +static u8 sx2010_read_reg (struct sx2010_data *data,u8 reg) +{ +int ret; +u8 val =0 ; + + data->tx_buf[0 ] =SX2010_CMD_READ ; + data->tx_buf[1 ] = reg; + + ret =sx2010_read_trans (data,2 ,1 ); +if (ret ==0 ) + val = data->rx_buf[0 ]; + +return val; +} + +static void sx2010_read_2regs (struct sx2010_data *data,u8 reg,u8 *v1,u8 *v2) +{ +int ret; + + data->tx_buf[0 ] =SX2010_CMD_READ ; + data->tx_buf[1 ] = reg; + + ret =sx2010_read_trans (data,2 ,2 ); +if (ret ==0 ) { + *v1 = data->rx_buf[0 ]; + *v2 = data->rx_buf[1 ]; + } +} + +static void sx2010_write_reg (struct sx2010_data *data,u8 reg,u8 val) +{ + data->tx_buf[0 ] =SX2010_CMD_WRITE ; + data->tx_buf[1 ] = reg; + data->tx_buf[2 ] = val; + +sx2010_write_trans (data,2 ,1 ); +} + +static void sx2010_write_2regs (struct sx2010_data *data,u8 reg,u8 v1,u8 v2) +{ + data->tx_buf[0 ] =SX2010_CMD_WRITE ; + data->tx_buf[1 ] = reg; + data->tx_buf[2 ] = v1; + data->tx_buf[3 ] = v2; + +sx2010_write_trans (data,2 ,2 ); +} + +static void sx2010_write_bits (struct sx2010_data *data,u8 reg,u8 mask,u8 val) +{ + data->tx_buf[0 ] =SX2010_CMD_BIT_MODIFY ; + data->tx_buf[1 ] = reg; + data->tx_buf[2 ] = mask; + data->tx_buf[3 ] = val; + +sx2010_write_trans (data,2 ,2 ); +} + +static void sx2010_hw_tx_frame (struct sx2010_data *data,u8 *buf, +int len,int tx_buf_idx) +{ +memcpy (data->tx_buf, buf,6 + len); +sx2010_write_trans (data,1 ,6 + len -1 ); +} + +static void sx2010_hw_tx (struct sx2010_data *data,struct can_frame *frame, +int tx_buf_idx) +{ +u32 sid, eid, exide, rtr; +u8 buf[SX2010_CAN_TRANS_BUF_LEN ]; + + exide = (frame->can_id &CAN_EFF_FLAG ) ?1 :0 ; +if (exide) + sid = (frame->can_id &CAN_EFF_MASK ) >>18 ; +else + sid = frame->can_id &CAN_SFF_MASK ; + eid = frame->can_id &CAN_EFF_MASK ; + rtr = (frame->can_id &CAN_RTR_FLAG ) ?1 :0 ; + + buf[0 ] =SX2010_CMD_WRITE_TXB (tx_buf_idx); + buf[1 ] = sid >>3 ; + buf[2 ] = ((sid &7 ) <<5 ) | (exide <<3 ) | ((eid >>16 ) &3 ); + buf[3 ] =GET_BYTE (eid,1 ); + buf[4 ] =GET_BYTE (eid,0 ); + buf[5 ] = (rtr <<6 ) | frame->len; + +memcpy (buf +6 , frame->data, frame->len); +sx2010_hw_tx_frame (data, buf, frame->len, tx_buf_idx); + + data->tx_buf[0 ] =SX2010_CMD_RTS (1 << tx_buf_idx); +sx2010_write_trans (data,1 ,0 ); +} + +static void sx2010_hw_rx_frame (struct sx2010_data *data,u8 *buf, +int buf_idx) +{ +memset (data->rx_buf,0 ,sizeof (u8) *SX2010_BUF_LEN ); + data->tx_buf[0 ] =SX2010_CMD_READ_RXB (buf_idx); + +sx2010_read_trans (data,1 ,SX2010_CAN_TRANS_BUF_LEN -1 ); +memcpy (&buf[1 ], data->rx_buf,SX2010_CAN_TRANS_BUF_LEN -1 ); +} + +static void sx2010_hw_rx (struct sx2010_data *data,int buf_idx) +{ +struct can_frame *frame; +struct sk_buff *skb; +u8 buf[SX2010_CAN_TRANS_BUF_LEN ]; + + skb =alloc_can_skb (data->net, &frame); +if (!skb) { +dev_err (data->dev,"cannot allocate RX skb\n" ); + data->net->stats.rx_dropped++; +return ; + } + +sx2010_hw_rx_frame (data, buf, buf_idx); +if (buf[2 ] ®_RXBSIDL_IDE ) { + frame->can_id =CAN_EFF_FLAG ; + frame->can_id |= +SET_BYTE (buf[2 ] ®_RXBSIDL_EID ,2 ) | +SET_BYTE (buf[3 ],1 ) | +SET_BYTE (buf[4 ],0 ) | + (((buf[1 ] <<3 ) | (buf[1 ] >>5 )) <<18 ); + +if (buf[5 ] ®_RXBDLC_RTR ) + frame->can_id |=CAN_RTR_FLAG ; + }else { + frame->can_id = (buf[1 ] <<3 ) | (buf[2 ] >>5 ); + +if (buf[2 ] ®_RXBSIDL_SRR ) + frame->can_id |=CAN_RTR_FLAG ; + } + + frame->len =can_cc_dlc2len (buf[5 ] ®_RXBDLC_LEN_MASK ); +memcpy (frame->data, buf +6 , frame->len); + + data->net->stats.rx_packets++; + data->net->stats.rx_bytes += frame->len; + +can_led_event (data->net,CAN_LED_EVENT_RX ); +netif_rx_ni (skb); +} + +static void sx2010_hw_sleep (struct sx2010_data *data) +{ +sx2010_write_reg (data,REG_CTRL ,REG_CTRL_SLEEP ); +} + +static int sx2010_hw_startup (struct sx2010_data *data) +{ +int divisor =1 ; +u32 val; + + divisor = data->clk_rate /SX2010_FREQUENCY ; + val =0x01 ; + val |= ((divisor %256 ) <<16 ); + val |= ((divisor /256 ) <<24 ); +sx2010_writel (data,SX2010_REG_CTRL , val); +sx2010_writel (data,SX2010_REG_IRQ_ENABLE ,0 ); +sx2010_writel (data,SX2010_REG_GPIO_OUT_ENABLE , data->chip_select); +sx2010_writel (data,SX2010_REG_GPIO_OUT , data->chip_select); +return 0 ; +} + +static int sx2010_hw_shutdown (struct sx2010_data *data) +{ +sx2010_writel (data,SX2010_REG_CTRL ,0 ); +sx2010_writel (data,SX2010_REG_IRQ_ENABLE ,0 ); +sx2010_writel (data,SX2010_REG_GPIO_OUT_ENABLE , data->chip_select); +sx2010_writel (data,SX2010_REG_GPIO_OUT ,0 ); +return 0 ; +} + +static int sx2010_hw_reset (struct sx2010_data *data) +{ +unsigned long timeout; +int ret; + +mdelay (SX2010_OST_DELAY_MS ); + + data->tx_buf[0 ] =SX2010_CMD_RESET ; + ret =sx2010_write_trans (data,1 ,0 ); +if (ret) +return ret; + +mdelay (SX2010_OST_DELAY_MS ); + + timeout = jiffies +HZ ; +while ((sx2010_read_reg (data,REG_STAT ) ®_CTRL_MASK ) != +REG_CTRL_CONF ) { +usleep_range (SX2010_OST_DELAY_MS *1000 , +SX2010_OST_DELAY_MS *1000 *2 ); + +if (time_after (jiffies, timeout)) { +dev_err (data->dev,"SX2010 didn't enter in config mode\n" ); +return -EBUSY ; + } + } +return 0 ; +} + +static int sx2010_hw_probe (struct sx2010_data *data) +{ +int ret; +u8 val; + + ret =sx2010_hw_startup (data); +if (ret) +return ret; + + ret =sx2010_hw_reset (data); +if (ret) +return ret; + + val =sx2010_read_reg (data,REG_CTRL ); +if ((val &0x17 ) !=0x07 ) +return -ENODEV ; +return 0 ; +} + +static netdev_tx_t sx2010_start_xmit (struct sk_buff *skb, +struct net_device *net) +{ +struct sx2010_data *data =netdev_priv (net); + +if (data->tx_skb || data->tx_len) { +dev_warn (data->dev,"xmit called while tx busy\n" ); +return NETDEV_TX_BUSY ; + } + +if (can_dropped_invalid_skb (net, skb)) +return NETDEV_TX_OK ; + +netif_stop_queue (net); + data->tx_skb = skb; +queue_work (data->wq, &data->tx_work); + +return NETDEV_TX_OK ; +} + +static int sx2010_do_set_mode (struct net_device *net,enum can_mode mode) +{ +struct sx2010_data *data =netdev_priv (net); + +switch (mode) { +case CAN_MODE_START : +sx2010_clean (net); + data->can.state =CAN_STATE_ERROR_ACTIVE ; + data->restart_tx =1 ; +if (data->can.restart_ms ==0 ) + data->after_suspend =SX2010_AFTER_SUSPEND_RESTART ; +queue_work (data->wq, &data->restart_work); +break ; +default : +return -EOPNOTSUPP ; + } + +return 0 ; +} + +static int sx2010_set_normal_mode (struct sx2010_data *data) +{ +unsigned long timeout; + +sx2010_write_reg (data,REG_INTE ,REG_INTE_ALL ); + +if (data->can.ctrlmode &CAN_CTRLMODE_LOOPBACK ) { +sx2010_write_reg (data,REG_CTRL ,REG_CTRL_LOOPBACK ); + }else if (data->can.ctrlmode &CAN_CTRLMODE_LISTENONLY ) { +sx2010_write_reg (data,REG_CTRL ,REG_CTRL_LISTEN_ONLY ); + }else { +sx2010_write_reg (data,REG_CTRL ,REG_CTRL_NORMAL ); + + timeout = jiffies +HZ ; +while (sx2010_read_reg (data,REG_STAT ) ®_CTRL_MASK ) { +schedule (); +if (time_after (jiffies, timeout)) { +dev_err (data->dev,"SX2010 didn't enter in normal mode\n" ); +return -EBUSY ; + } + } + } + data->can.state =CAN_STATE_ERROR_ACTIVE ; +return 0 ; +} + +static int sx2010_do_set_bittiming (struct net_device *net) +{ +struct sx2010_data *data =netdev_priv (net); +struct can_bittiming *bt = &data->can.bittiming; + +sx2010_write_reg (data,REG_CFG1 , ((bt->sjw -1 ) <<6 ) | + (bt->brp -1 )); +sx2010_write_reg (data,REG_CFG2 ,REG_CFG2_BTLMODE | + (data->can.ctrlmode &CAN_CTRLMODE_3_SAMPLES ? +REG_CFG2_SAM :0 ) | + ((bt->phase_seg1 -1 ) <<3 ) | + (bt->prop_seg -1 )); +sx2010_write_bits (data,REG_CFG3 ,REG_CFG3_PHSEG2_MASK , + (bt->phase_seg2 -1 )); +dev_dbg (data->dev,"CFG: 0x%02x 0x%02x 0x%02x\n" , +sx2010_read_reg (data,REG_CFG1 ), +sx2010_read_reg (data,REG_CFG2 ), +sx2010_read_reg (data,REG_CFG3 )); + +return 0 ; +} + +static int sx2010_setup (struct net_device *net,struct sx2010_data *data) +{ +sx2010_do_set_bittiming (net); + +sx2010_write_reg (data,REG_RXBCTRL (0 ), +REG_RXBCTRL_BUKT |REG_RXBCTRL_RXM0 |REG_RXBCTRL_RXM1 ); +sx2010_write_reg (data,REG_RXBCTRL (1 ), +REG_RXBCTRL_RXM0 |REG_RXBCTRL_RXM1 ); +return 0 ; +} + +static int sx2010_stop (struct net_device *net) +{ +struct sx2010_data *data =netdev_priv (net); + +close_candev (net); + + data->force_quit =1 ; +del_timer (&data->timer); + +mutex_lock (&data->sx_lock); + +sx2010_write_2regs (data,REG_INTE ,0x00 ,0x00 ); +sx2010_write_reg (data,REG_TXBCTRL (0 ),0 ); +sx2010_clean (net); + +sx2010_hw_sleep (data); + + data->can.state =CAN_STATE_STOPPED ; + +mutex_unlock (&data->sx_lock); + +can_led_event (net,CAN_LED_EVENT_STOP ); +return 0 ; +} + +static void sx2010_error_skb (struct net_device *net,int can_id,int data1) +{ +struct can_frame *frame; +struct sk_buff *skb; + + skb =alloc_can_err_skb (net, &frame); +if (skb) { + frame->can_id |= can_id; + frame->data[1 ] = data1; +netif_rx_ni (skb); + }else { +netdev_err (net,"cannot allocate error skb\n" ); + } +} + +static void sx2010_tx_work_handler (struct work_struct *ws) +{ + struct sx2010_data *data = container_of(ws, struct sx2010_data, + tx_work); +struct net_device *net = data->net; +struct can_frame *frame; + +mutex_lock (&data->sx_lock); +if (data->tx_skb) { +if (data->can.state ==CAN_STATE_BUS_OFF ) { +sx2010_clean (net); + }else { + frame = (struct can_frame *)data->tx_skb->data; + +if (frame->can_dlc >SX2010_CAN_MAX_DATA_LEN ) + frame->can_dlc =SX2010_CAN_MAX_DATA_LEN ; + +sx2010_hw_tx (data, frame,0 ); + data->tx_len =1 + frame->len; +can_put_echo_skb (data->tx_skb, net,0 ,0 ); + data->tx_skb =NULL ; + } + } +mutex_unlock (&data->sx_lock); +} + +static void sx2010_restart_work_handler (struct work_struct *ws) +{ + struct sx2010_data *data = container_of(ws, struct sx2010_data, + restart_work); +struct net_device *net = data->net; + +mutex_lock (&data->sx_lock); +if (data->after_suspend) { +sx2010_hw_reset (data); +sx2010_setup (net, data); + data->force_quit =0 ; +if (data->after_suspend &SX2010_AFTER_SUSPEND_RESTART ) { +sx2010_set_normal_mode (data); + }else if (data->after_suspend &SX2010_AFTER_SUSPEND_UP ) { +netif_device_attach (net); +sx2010_clean (net); +sx2010_set_normal_mode (data); +netif_wake_queue (net); + }else { +sx2010_hw_sleep (data); + } + data->after_suspend =0 ; + } + +if (data->restart_tx) { + data->restart_tx =0 ; +sx2010_write_reg (data,REG_TXBCTRL (0 ),0 ); +sx2010_clean (net); +netif_wake_queue (net); +sx2010_error_skb (net,CAN_ERR_RESTARTED ,0 ); + } +mutex_unlock (&data->sx_lock); +} + +static void sx2010_can_isr (struct timer_list *t) +{ +struct sx2010_data *data =from_timer (data, t, timer); +struct net_device *net = data->net; +int cnt =SX2010_ISR_MAX_CNT ; + +if (!mutex_trylock (&data->sx_lock)) + goto out_without_unlock; + +while (!data->force_quit) { +enum can_state new_state; +u8 intf, eflag; +u8 clear_intf =0 ; +int can_id =0 , data1 =0 ; + +sx2010_read_2regs (data,REG_INTF , &intf, &eflag); + + intf &=REG_INTF_RX |REG_INTF_TX |REG_INTF_ERR ; + +if (intf ®_INTF_RX0 ) +sx2010_hw_rx (data,0 ); +if (intf ®_INTF_RX1 ) +sx2010_hw_rx (data,1 ); + +if (intf & (REG_INTF_ERR |REG_INTF_TX )) + clear_intf |= intf & (REG_INTF_ERR |REG_INTF_TX ); +if (clear_intf) +sx2010_write_bits (data,REG_INTF , clear_intf,0x00 ); +if (eflag & (REG_EFLG_RX0OVR |REG_EFLG_RX1OVR )) +sx2010_write_bits (data,REG_EFLG , eflag,0x00 ); + +if (eflag ®_EFLG_TXBO ) { + new_state =CAN_STATE_BUS_OFF ; + can_id |=CAN_ERR_BUSOFF ; + }else if (eflag ®_EFLG_TXEP ) { + new_state =CAN_STATE_ERROR_PASSIVE ; + can_id |=CAN_ERR_CRTL ; + data1 |=CAN_ERR_CRTL_TX_PASSIVE ; + }else if (eflag ®_EFLG_RXEP ) { + new_state =CAN_STATE_ERROR_PASSIVE ; + can_id |=CAN_ERR_CRTL ; + data1 |=CAN_ERR_CRTL_RX_PASSIVE ; + }else if (eflag ®_EFLG_TXWAR ) { + new_state =CAN_STATE_ERROR_WARNING ; + can_id |=CAN_ERR_CRTL ; + data1 |=CAN_ERR_CRTL_TX_WARNING ; + }else if (eflag ®_EFLG_RXWAR ) { + new_state =CAN_STATE_ERROR_WARNING ; + can_id |=CAN_ERR_CRTL ; + data1 |=CAN_ERR_CRTL_RX_WARNING ; + }else { + new_state =CAN_STATE_ERROR_ACTIVE ; + } + +switch (data->can.state) { +case CAN_STATE_ERROR_ACTIVE : +if (new_state >=CAN_STATE_ERROR_WARNING && + new_state <=CAN_STATE_BUS_OFF ) + data->can.can_stats.error_warning++; + fallthrough; +case CAN_STATE_ERROR_WARNING : +if (new_state >=CAN_STATE_ERROR_PASSIVE && + new_state <=CAN_STATE_BUS_OFF ) + data->can.can_stats.error_passive++; +break ; +default : +break ; + } + data->can.state = new_state; + +if (intf ®_INTF_ERR ) { +if (eflag & (REG_EFLG_RX0OVR |REG_EFLG_RX1OVR )) { +if (eflag ®_EFLG_RX0OVR ) { + net->stats.rx_over_errors++; + net->stats.rx_errors++; + } +if (eflag ®_EFLG_RX1OVR ) { + net->stats.rx_over_errors++; + net->stats.rx_errors++; + } + can_id |=CAN_ERR_CRTL ; + data1 |=CAN_ERR_CRTL_RX_OVERFLOW ; + } +sx2010_error_skb (net, can_id, data1); + } + +if (data->can.state ==CAN_STATE_BUS_OFF ) { +if (data->can.restart_ms ==0 ) { + data->force_quit =1 ; + data->can.can_stats.bus_off++; +can_bus_off (net); +sx2010_hw_sleep (data); +break ; + } + } + +if (intf ==0 ) +break ; + +if (intf ®_INTF_TX ) { + net->stats.tx_packets++; + net->stats.tx_bytes += data->tx_len -1 ; +can_led_event (net,CAN_LED_EVENT_TX ); +if (data->tx_len) { +can_get_echo_skb (net,0 ,NULL ); + data->tx_len =0 ; + } +netif_wake_queue (net); + } + +if (--cnt <=0 ) +break ; + } +mutex_unlock (&data->sx_lock); + +out_without_unlock: +mod_timer (&data->timer, jiffies +SX2010_ISR_TIME ); +} + +static int sx2010_open (struct net_device *net) +{ +struct sx2010_data *data =netdev_priv (net); +int ret; + + ret =open_candev (net); +if (ret) { +dev_err (data->dev,"unable to set initial baudrate!\n" ); +return ret; + } + +mutex_lock (&data->sx_lock); + + data->force_quit =0 ; + data->tx_skb =NULL ; + data->tx_len =0 ; + + ret =sx2010_hw_reset (data); +if (ret) + goto out_clean; + + ret =sx2010_setup (net, data); +if (ret) + goto out_clean; + + ret =sx2010_set_normal_mode (data); +if (ret) + goto out_clean; + +can_led_event (net,CAN_LED_EVENT_OPEN ); + +netif_wake_queue (net); +mutex_unlock (&data->sx_lock); + +add_timer (&data->timer); +mod_timer (&data->timer, jiffies +SX2010_ISR_TIME ); +return 0 ; + +out_clean: +sx2010_hw_sleep (data); +close_candev (net); +mutex_unlock (&data->sx_lock); +return ret; +} + +static const struct net_device_ops sx2010_netdev_ops = { + .ndo_open = sx2010_open, + .ndo_stop = sx2010_stop, + .ndo_start_xmit = sx2010_start_xmit, + .ndo_change_mtu = can_change_mtu, +}; + +static int sx2010_read_property (struct sx2010_can_data *data, +struct device *dev) +{ +int ret; + + ret =device_property_read_u32 (dev,"board_id" , &data->board_id); +if (ret <0 ) +return -EINVAL ; + ret =device_property_read_u8 (dev,"chl_number" , &data->chl_number); +if (ret <0 ) +return -EINVAL ; + ret =device_property_read_u8 (dev,"version" , &data->version); +if (ret <0 ) +return -EINVAL ; + ret =device_property_read_u32 (dev,"significand" , &data->significand); +if (ret <0 ) +return -EINVAL ; + ret =device_property_read_u8 (dev,"exponent" , &data->exponent); +if (ret <0 ) +return -EINVAL ; + ret =device_property_read_u8 (dev,"gpio_input" , &data->gpio_input); +if (ret <0 ) +return -EINVAL ; + ret =device_property_read_u8 (dev,"gpio_output" , &data->gpio_output); +if (ret <0 ) +return -EINVAL ; + +return 0 ; +} + +#ifdef CONFIG_DEBUG_FS + +static struct dentry *sx2010_debugfs; +#define SX2010_DEBUGFS_FORMAT "b%d:can%d" + +static void sx2010_debugfs_add (struct sx2010_data *data) +{ +struct dentry *dir; +char name[32 ]; + +snprintf (name,sizeof (name),SX2010_DEBUGFS_FORMAT , + data->data.board_id, data->data.chl_number); + dir =debugfs_create_dir (name, sx2010_debugfs); +debugfs_create_x8 ("version" ,0444 , dir, &data->data.version); +} + +static void sx2010_debugfs_remove (struct sx2010_data *data) +{ +struct dentry *dir; +char name[32 ]; + +snprintf (name,sizeof (name),SX2010_DEBUGFS_FORMAT , + data->data.board_id, data->data.chl_number); + dir =debugfs_lookup (name, sx2010_debugfs); +debugfs_remove_recursive (dir); +} + +static void sx2010_debugfs_init (void ) +{ + sx2010_debugfs =debugfs_create_dir (DRIVER_NAME ,NULL ); +} + +static void sx2010_debugfs_exit (void ) +{ +debugfs_remove (sx2010_debugfs); +} +#else +static void sx2010_debugfs_add (struct sx2010_data *data) {} +static void sx2010_debugfs_remove (struct sx2010_data *data) {} +static void sx2010_debugfs_init (void ) {} +static void sx2010_debugfs_exit (void ) {} +#endif + +static int sx2010_probe (struct platform_device *pdev) +{ +unsigned long long exponent; +struct sx2010_data *data; +struct net_device *net; +struct resource *res; +struct device *dev; +int ret; +int i; + + dev = &pdev->dev; + + res =platform_get_resource (pdev,IORESOURCE_MEM ,0 ); +if (!res) { +dev_err (dev,"no resource defined\n" ); +return -EINVAL ; + } + + net =alloc_candev (sizeof (*data),SX2010_TX_ECHO_SKB_MAX ); +if (!net) +return -ENOMEM ; + + data =netdev_priv (net); +if (!data) + goto err_out_free; + + ret =sx2010_read_property (&data->data, dev); +if (ret <0 ) { +dev_err (dev,"failed to read property\n" ); + goto err_out_free; + } + data->dev = dev; + data->chip_select = data->data.gpio_output; + data->res = res; + + exponent =1 ; +for (i =0 ; i < data->data.exponent; i++) + exponent *=10 ; + data->clk_rate = data->data.significand * exponent; + + data->base =devm_ioremap (dev, res->start,resource_size (res)); +if (!data->base) { + ret = -ENOMEM ; + goto err_out_free; + } + + net->netdev_ops = &sx2010_netdev_ops; + net->flags |=IFF_ECHO ; + + data->can.bittiming_const = &sx2010_bittiming_const; + data->can.do_set_mode = sx2010_do_set_mode; + data->can.clock.freq =SX2010_FREQUENCY /2 ; + data->can.ctrlmode_supported =CAN_CTRLMODE_3_SAMPLES | +CAN_CTRLMODE_LOOPBACK |CAN_CTRLMODE_LISTENONLY ; + + data->net = net; +platform_set_drvdata (pdev, data); + + data->wq =alloc_workqueue ("sx2010_wq" ,WQ_FREEZABLE |WQ_MEM_RECLAIM , +0 ); +if (!data->wq) { + ret = -ENOMEM ; + goto err_out_free; + } +INIT_WORK (&data->tx_work, sx2010_tx_work_handler); +INIT_WORK (&data->restart_work, sx2010_restart_work_handler); + +mutex_init (&data->trans_lock); +mutex_init (&data->sx_lock); + + data->tx_buf =devm_kzalloc (data->dev,SX2010_BUF_LEN ,GFP_KERNEL ); +if (!data->tx_buf) { + ret = -ENOMEM ; + goto err_out_probe; + } + + data->rx_buf =devm_kzalloc (data->dev,SX2010_BUF_LEN ,GFP_KERNEL ); +if (!data->rx_buf) { + ret = -ENOMEM ; + goto err_out_probe; + } + +SET_NETDEV_DEV (net, dev); + + ret =sx2010_hw_probe (data); +if (ret) { +if (ret == -ENODEV ) +dev_err (data->dev,"cannot initialize SX2010.\n" ); + goto err_out_probe; + } + +sx2010_hw_sleep (data); + + ret =register_candev (net); +if (ret) + goto err_out_probe; + +devm_can_led_init (net); + +sx2010_debugfs_add (data); +timer_setup (&data->timer, sx2010_can_isr,0 ); +netdev_info (net,"SX2010 successfully initialized.\n" ); +return 0 ; + +err_out_probe: +destroy_workqueue (data->wq); + data->wq =NULL ; + +err_out_free: +free_candev (net); + +dev_err (dev,"failed to probe, err %d\n" , ret); +return ret; +} + +static int sx2010_remove (struct platform_device *pdev) +{ +struct sx2010_data *data =platform_get_drvdata (pdev); +struct net_device *net = data->net; + +sx2010_debugfs_remove (data); +del_timer (&data->timer); +unregister_candev (net); + +destroy_workqueue (data->wq); + data->wq =NULL ; + +sx2010_hw_shutdown (data); +free_candev (net); +return 0 ; +} + +#ifdef CONFIG_PM_SLEEP +static int sx2010_suspend (struct device *dev) +{ +struct sx2010_data *data =dev_get_drvdata (dev); +struct net_device *net = data->net; + + data->force_quit =1 ; +del_timer (&data->timer); + +if (netif_running (net)) { +netif_device_detach (net); + +sx2010_hw_sleep (data); + data->after_suspend =SX2010_AFTER_SUSPEND_UP ; + }else { + data->after_suspend =SX2010_AFTER_SUSPEND_DOWN ; + } + +sx2010_hw_shutdown (data); +return 0 ; +} + +static int sx2010_resume (struct device *dev) +{ +struct sx2010_data *data =dev_get_drvdata (dev); + +sx2010_hw_startup (data); + +if (data->after_suspend &SX2010_AFTER_SUSPEND_UP ) { +add_timer (&data->timer); +mod_timer (&data->timer, jiffies +SX2010_ISR_TIME ); +queue_work (data->wq, &data->restart_work); + }else { + data->after_suspend =0 ; + } + + data->force_quit =0 ; +return 0 ; +} +#endif + +static const struct dev_pm_ops sx2010_pm_ops = { +SET_SYSTEM_SLEEP_PM_OPS (sx2010_suspend, sx2010_resume) +}; + +static struct platform_driver sx2010_platform_driver = { + .driver = { + .name =DRIVER_NAME , + .pm = &sx2010_pm_ops, + .suppress_bind_attrs = true, + }, + .probe = sx2010_probe, + .remove = sx2010_remove, +}; + +static int __init sx2010_init (void ) +{ +sx2010_debugfs_init (); +platform_driver_register (&sx2010_platform_driver); +return 0 ; +} +module_init (sx2010_init); + +static void __exit sx2010_exit(void) +{ +platform_driver_unregister (&sx2010_platform_driver); +sx2010_debugfs_exit (); +} +module_exit (sx2010_exit); + +MODULE_AUTHOR ("Jason Lee <[email protected]>" ); +MODULE_DESCRIPTION ("SUNIX SDC CAN controller driver" ); +MODULE_LICENSE ("GPL v2" ); +MODULE_ALIAS ("platform:" DRIVER_NAME );
Signature · 1 line
2.20.1
Marc Kleine-Budde
maintainerQuoted 3 lines · Moriis Ku
From: Morris Ku <[email protected]>
Add support for SUNIX SDC CAN controller
How is your hardware attached? Looks a bit like SPI. You should write a SPI host driver first.
Marc
Signature · 4 lines
Pengutronix e.K. | Marc Kleine-Budde | Embedded Linux | https://www.pengutronix.de | Vertretung West/Dortmund | Phone: +49-231-2826-924 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
Marc Kleine-Budde
maintainerQuoted 27 lines · Moriis Ku
From: Morris Ku <[email protected]>
Add support for SUNIX SDC CAN controller
Cc Jason Lee <[email protected]>Cc Taian Chen <[email protected]>Cc Morris Ku <[email protected]>Cc Edward Lee <[email protected]>Signed-off-by Morris Ku <[email protected]>--- sx2010_can.c | 1243 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1243 insertions(+) create mode 100644 sx2010_can.csx2010_can.c new file +7
-0@@ -0,0 +1,1243 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* SUNIX SDC CAN controller driver. +* +* Copyright (C) 2021, SUNIX Co., Ltd. +* +*/
[...]
sx2010_can.c line 1243
Quoted 4 lines
sx2010_can.c new file +4
-0+MODULE_AUTHOR ("Jason Lee <[email protected]>" ); +MODULE_DESCRIPTION ("SUNIX SDC CAN controller driver" ); +MODULE_LICENSE ("GPL v2" ); +MODULE_ALIAS ("platform:" DRIVER_NAME );
This driver looks like a 99% copy of the mcp251x.c driver, but your copyrights state something different. :(
Marc
Signature · 4 lines
Pengutronix e.K. | Marc Kleine-Budde | Embedded Linux | https://www.pengutronix.de | Vertretung West/Dortmund | Phone: +49-231-2826-924 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |