summaryrefslogtreecommitdiff
path: root/drivers/net/can/old/ccan/ccan.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/can/old/ccan/ccan.c')
-rw-r--r--drivers/net/can/old/ccan/ccan.c557
1 files changed, 557 insertions, 0 deletions
diff --git a/drivers/net/can/old/ccan/ccan.c b/drivers/net/can/old/ccan/ccan.c
new file mode 100644
index 000000000000..6a908a14eee4
--- /dev/null
+++ b/drivers/net/can/old/ccan/ccan.c
@@ -0,0 +1,557 @@
+/*
+ * drivers/can/c_can.c
+ *
+ * Copyright (C) 2007
+ *
+ * - Sascha Hauer, Marc Kleine-Budde, Pengutronix
+ * - Simon Kallweit, intefo AG
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the version 2 of the GNU General Public License
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifdef CONFIG_CAN_DEBUG_DEVICES
+#define DBG(args...) printk(args)
+#else
+#define DBG(args...)
+#endif
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/can.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/io.h>
+
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+#include "ccan.h"
+
+static u32 ccan_read_reg32(struct net_device *dev, enum c_regs reg)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+
+ u32 val = priv->read_reg(dev, reg);
+ val |= ((u32) priv->read_reg(dev, reg + 2)) << 16;
+
+ return val;
+}
+
+static void ccan_write_reg32(struct net_device *dev, enum c_regs reg, u32 val)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+
+ priv->write_reg(dev, reg, val & 0xffff);
+ priv->write_reg(dev, reg + 2, val >> 16);
+}
+
+static inline void ccan_object_get(struct net_device *dev,
+ int iface, int objno, int mask)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+
+ priv->write_reg(dev, CAN_IF_COMM(iface), mask);
+ priv->write_reg(dev, CAN_IF_COMR(iface), objno + 1);
+ while (priv->read_reg(dev, CAN_IF_COMR(iface)) & IF_COMR_BUSY)
+ DBG("busy\n");
+}
+
+static inline void ccan_object_put(struct net_device *dev,
+ int iface, int objno, int mask)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+
+ priv->write_reg(dev, CAN_IF_COMM(iface), IF_COMM_WR | mask);
+ priv->write_reg(dev, CAN_IF_COMR(iface), objno + 1);
+ while (priv->read_reg(dev, CAN_IF_COMR(iface)) & IF_COMR_BUSY)
+ DBG("busy\n");
+}
+
+static int ccan_write_object(struct net_device *dev,
+ int iface, struct can_frame *frame, int objno)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+ unsigned int val;
+
+ if (frame->can_id & CAN_EFF_FLAG)
+ val = IF_ARB_MSGXTD | (frame->can_id & CAN_EFF_MASK);
+ else
+ val = ((frame->can_id & CAN_SFF_MASK) << 18);
+
+ if (!(frame->can_id & CAN_RTR_FLAG))
+ val |= IF_ARB_TRANSMIT;
+
+ val |= IF_ARB_MSGVAL;
+ ccan_write_reg32(dev, CAN_IF_ARB(iface), val);
+
+ memcpy(&val, &frame->data[0], 4);
+ ccan_write_reg32(dev, CAN_IF_DATAA(iface), val);
+ memcpy(&val, &frame->data[4], 4);
+ ccan_write_reg32(dev, CAN_IF_DATAB(iface), val);
+ priv->write_reg(dev, CAN_IF_MCONT(iface),
+ IF_MCONT_TXIE | IF_MCONT_TXRQST | IF_MCONT_EOB |
+ (frame->can_dlc & 0xf));
+
+ ccan_object_put(dev, 0, objno, IF_COMM_ALL);
+
+ return 0;
+}
+
+static int ccan_read_object(struct net_device *dev, int iface, int objno)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+ unsigned int val, ctrl, data;
+ struct sk_buff *skb;
+ struct can_frame *frame;
+
+ skb = dev_alloc_skb(sizeof(struct can_frame));
+ skb->dev = dev;
+
+ ccan_object_get(dev, 0, objno, IF_COMM_ALL & ~IF_COMM_TXRQST);
+#ifdef CCAN_DEBUG
+ priv->bufstat[objno]++;
+#endif
+ frame = (struct can_frame *)skb_put(skb, sizeof(struct can_frame));
+
+ ctrl = priv->read_reg(dev, CAN_IF_MCONT(iface));
+
+ if (ctrl & IF_MCONT_MSGLST) {
+ priv->can.net_stats.rx_errors++;
+ DBG("%s: msg lost in buffer %d\n", __func__, objno);
+ }
+
+ frame->can_dlc = ctrl & 0xf;
+
+ val = ccan_read_reg32(dev, CAN_IF_ARB(iface));
+
+ data = ccan_read_reg32(dev, CAN_IF_DATAA(iface));
+ memcpy(&frame->data[0], &data, 4);
+ data = ccan_read_reg32(dev, CAN_IF_DATAB(iface));
+ memcpy(&frame->data[4], &data, 4);
+
+ if (val & IF_ARB_MSGXTD)
+ frame->can_id = (val & CAN_EFF_MASK) | CAN_EFF_FLAG;
+ else
+ frame->can_id = (val >> 18) & CAN_SFF_MASK;
+
+ if (val & IF_ARB_TRANSMIT)
+ frame->can_id |= CAN_RTR_FLAG;
+
+ priv->write_reg(dev, CAN_IF_MCONT(iface), ctrl &
+ ~(IF_MCONT_MSGLST | IF_MCONT_INTPND | IF_MCONT_NEWDAT));
+
+ ccan_object_put(dev, 0, objno, IF_COMM_CONTROL);
+
+ skb->protocol = __constant_htons(ETH_P_CAN);
+ netif_rx(skb);
+
+ priv->can.net_stats.rx_packets++;
+ priv->can.net_stats.rx_bytes += frame->can_dlc;
+
+ return 0;
+}
+
+static int ccan_setup_receive_object(struct net_device *dev, int iface,
+ int objno, unsigned int mask,
+ unsigned int id, unsigned int mcont)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+
+ ccan_write_reg32(dev, CAN_IF_MASK(iface), mask);
+ ccan_write_reg32(dev, CAN_IF_ARB(iface), IF_ARB_MSGVAL | id);
+
+ priv->write_reg(dev, CAN_IF_MCONT(iface), mcont);
+
+ ccan_object_put(dev, 0, objno, IF_COMM_ALL & ~IF_COMM_TXRQST);
+
+ DBG("%s: obj no %d msgval: 0x%08x\n", __func__,
+ objno, ccan_read_reg32(dev, CAN_MSGVAL));
+
+ return 0;
+}
+
+static int ccan_inval_object(struct net_device *dev, int iface, int objno)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+
+ ccan_write_reg32(dev, CAN_IF_ARB(iface), 0);
+ priv->write_reg(dev, CAN_IF_MCONT(iface), 0);
+ ccan_object_put(dev, 0, objno, IF_COMM_ARB | IF_COMM_CONTROL);
+
+ DBG("%s: obj no %d msgval: 0x%08x\n", __func__,
+ objno, ccan_read_reg32(dev, CAN_MSGVAL));
+
+ return 0;
+}
+
+static int ccan_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+ struct can_frame *frame = (struct can_frame *)skb->data;
+
+ spin_lock_irq(&priv->can.irq_lock);
+
+ ccan_write_object(dev, 0, frame, priv->tx_object);
+#ifdef CCAN_DEBUG
+ priv->bufstat[priv->tx_object]++;
+#endif
+ priv->tx_object++;
+ if (priv->tx_object > 5)
+ netif_stop_queue(dev);
+
+ spin_unlock_irq(&priv->can.irq_lock);
+
+ priv->can.net_stats.tx_packets++;
+ priv->can.net_stats.tx_bytes += frame->can_dlc;
+
+ dev->trans_start = jiffies;
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+static void ccan_tx_timeout(struct net_device *dev)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+
+ priv->can.net_stats.tx_errors++;
+}
+
+static int ccan_set_bittime(struct net_device *dev, struct can_bittime *br)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+ unsigned int reg_timing, ctrl_save;
+ u8 brp, sjw, tseg1, tseg2;
+
+ if (br->type != CAN_BITTIME_STD)
+ return -EINVAL;
+
+ brp = br->std.brp - 1;
+ sjw = br->std.sjw - 1;
+ tseg1 = br->std.prop_seg + br->std.phase_seg1 - 1;
+ tseg2 = br->std.phase_seg2 - 1;
+
+ reg_timing = (brp & BTR_BRP_MASK) |
+ ((sjw << BTR_SJW_SHIFT) & BTR_SJW_MASK) |
+ ((tseg1 << BTR_TSEG1_SHIFT) & BTR_TSEG1_MASK) |
+ ((tseg2 << BTR_TSEG2_SHIFT) & BTR_TSEG2_MASK);
+
+ DBG("%s: brp = %d sjw = %d seg1 = %d seg2 = %d\n", __func__,
+ brp, sjw, tseg1, tseg2);
+ DBG("%s: setting BTR to %04x\n", __func__, reg_timing);
+
+ spin_lock_irq(&priv->can.irq_lock);
+
+ ctrl_save = priv->read_reg(dev, CAN_CONTROL);
+ priv->write_reg(dev, CAN_CONTROL,
+ ctrl_save | CONTROL_CCE | CONTROL_INIT);
+ priv->write_reg(dev, CAN_BTR, reg_timing);
+ priv->write_reg(dev, CAN_CONTROL, ctrl_save);
+
+ spin_unlock_irq(&priv->can.irq_lock);
+
+ return 0;
+}
+
+static int ccan_set_mode(struct net_device *dev, enum can_mode mode)
+{
+ switch (mode) {
+ case CAN_MODE_START:
+ DBG("%s: CAN_MODE_START requested\n", __func__);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int ccan_get_state(struct net_device *dev, enum can_state *state)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+ u32 reg_status;
+#ifdef CCAN_DEBUG
+ int i;
+#endif
+
+ reg_status = priv->read_reg(dev, CAN_STATUS);
+
+ if (reg_status & STATUS_EPASS)
+ *state = CAN_STATE_BUS_PASSIVE;
+ else if (reg_status & STATUS_EWARN)
+ *state = CAN_STATE_BUS_WARNING;
+ else if (reg_status & STATUS_BOFF)
+ *state = CAN_STATE_BUS_OFF;
+ else
+ *state = CAN_STATE_ACTIVE;
+#ifdef CCAN_DEBUG
+ DBG("buffer statistic:\n");
+ for (i = 0; i <= MAX_OBJECT; i++)
+ DBG("%d: %d\n", i, priv->bufstat[i]);
+#endif
+ return 0;
+}
+
+static int ccan_do_status_irq(struct net_device *dev)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+ int status, diff;
+
+ status = priv->read_reg(dev, CAN_STATUS);
+ status &= ~(STATUS_TXOK | STATUS_RXOK);
+ diff = status ^ priv->last_status;
+
+ if (diff & STATUS_EPASS) {
+ if (status & STATUS_EPASS)
+ dev_info(ND2D(dev), "entered error passive state\n");
+ else
+ dev_info(ND2D(dev), "left error passive state\n");
+ }
+ if (diff & STATUS_EWARN) {
+ if (status & STATUS_EWARN)
+ dev_info(ND2D(dev), "entered error warning state\n");
+ else
+ dev_info(ND2D(dev), "left error warning state\n");
+ }
+ if (diff & STATUS_BOFF) {
+ if (status & STATUS_BOFF)
+ dev_info(ND2D(dev), "entered busoff state\n");
+ else
+ dev_info(ND2D(dev), "left busoff state\n");
+ }
+
+ if (diff & STATUS_LEC_MASK) {
+ switch (status & STATUS_LEC_MASK) {
+ case LEC_STUFF_ERROR:
+ dev_info(ND2D(dev), "suffing error\n");
+ break;
+ case LEC_FORM_ERROR:
+ dev_info(ND2D(dev), "form error\n");
+ break;
+ case LEC_ACK_ERROR:
+ dev_info(ND2D(dev), "ack error\n");
+ break;
+ case LEC_BIT1_ERROR:
+ dev_info(ND2D(dev), "bit1 error\n");
+ break;
+ }
+ }
+
+ priv->write_reg(dev, CAN_STATUS, 0);
+ priv->last_status = status;
+
+ return diff ? 1 : 0;
+}
+
+static void ccan_do_object_irq(struct net_device *dev, u16 irqstatus)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+ int i;
+ u32 val;
+
+ if (irqstatus > MAX_TRANSMIT_OBJECT) {
+ val = ccan_read_reg32(dev, CAN_NEWDAT);
+ while (val & RECEIVE_OBJECT_BITS) {
+ for (i = MAX_TRANSMIT_OBJECT + 1; i <= MAX_OBJECT; i++)
+ if (val & (1<<i))
+ ccan_read_object(dev, 0, i);
+ val = ccan_read_reg32(dev, CAN_NEWDAT);
+ }
+ } else {
+ ccan_inval_object(dev, 0, irqstatus - 1);
+ val = ccan_read_reg32(dev, CAN_TXRQST);
+ if (!val) {
+ priv->tx_object = 0;
+ netif_wake_queue(dev);
+ }
+ }
+}
+
+static void do_statuspoll(struct work_struct *work)
+{
+ struct ccan_priv *priv = container_of(((struct delayed_work *) work),
+ struct ccan_priv, work);
+
+ priv->write_reg(priv->dev, CAN_CONTROL,
+ CONTROL_SIE | CONTROL_EIE | CONTROL_IE);
+}
+
+static irqreturn_t ccan_isr(int irq, void *dev_id)
+{
+ struct net_device *dev = (struct net_device *) dev_id;
+ struct ccan_priv *priv = netdev_priv(dev);
+ u16 irqstatus;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->can.irq_lock, flags);
+
+ irqstatus = priv->read_reg(dev, CAN_IR);
+ while (irqstatus) {
+ if (irqstatus == 0x8000) {
+ if (ccan_do_status_irq(dev)) {
+ /* The c_can core tends to flood us with
+ * interrupts when certain error states don't
+ * disappear. Disable interrupts and see if it's
+ * getting better later. This is at least the
+ * case on the Magnachip h7202.
+ */
+ priv->write_reg(dev, CAN_CONTROL, CONTROL_EIE |
+ CONTROL_IE);
+ schedule_delayed_work(&priv->work, HZ / 10);
+ goto exit;
+ }
+ } else {
+ ccan_do_object_irq(dev, irqstatus);
+ }
+ irqstatus = priv->read_reg(dev, CAN_IR);
+ }
+
+exit:
+ spin_unlock_irqrestore(&priv->can.irq_lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static int ccan_open(struct net_device *dev)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+
+ if (request_irq(dev->irq, &ccan_isr, 0, dev->name, dev)) {
+ dev_err(ND2D(dev), "failed to attach interrupt\n");
+ return -EAGAIN;
+ }
+
+ priv->write_reg(dev, CAN_CONTROL,
+ CONTROL_EIE | CONTROL_SIE | CONTROL_IE);
+
+ netif_wake_queue(dev);
+
+ return 0;
+}
+
+static int ccan_stop(struct net_device *dev)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+ unsigned long flags;
+
+ netif_stop_queue(dev);
+
+ cancel_delayed_work(&priv->work);
+ flush_scheduled_work();
+
+ /* mask all IRQs */
+ spin_lock_irqsave(&priv->can.irq_lock, flags);
+ priv->write_reg(dev, CAN_CONTROL, 0);
+ spin_unlock_irqrestore(&priv->can.irq_lock, flags);
+
+ free_irq(dev->irq, dev);
+
+ return 0;
+}
+
+static int ccan_chip_config(struct net_device *dev)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+ int i;
+
+ /* setup message objects */
+ for (i = 0; i <= MAX_OBJECT; i++)
+ ccan_inval_object(dev, 0, i);
+
+ for (i = MAX_TRANSMIT_OBJECT + 1; i < MAX_OBJECT; i++)
+ ccan_setup_receive_object(dev, 0, i, 0, 0,
+ IF_MCONT_RXIE | IF_MCONT_UMASK);
+
+ ccan_setup_receive_object(dev, 0, MAX_OBJECT, 0, 0, IF_MCONT_EOB |
+ IF_MCONT_RXIE | IF_MCONT_UMASK);
+
+#ifdef CCAN_DEBUG
+ for (i = 0; i <= MAX_OBJECT; i++)
+ priv->bufstat[i] = 0;
+#endif
+
+ return 0;
+}
+
+struct net_device *alloc_ccandev(int sizeof_priv)
+{
+ struct net_device *dev;
+ struct ccan_priv *priv;
+
+ dev = alloc_candev(sizeof_priv);
+ if (!dev)
+ return NULL;
+
+ priv = netdev_priv(dev);
+
+ dev->open = ccan_open;
+ dev->stop = ccan_stop;
+ dev->hard_start_xmit = ccan_hard_start_xmit;
+ dev->tx_timeout = ccan_tx_timeout;
+
+ priv->can.bitrate = 500000;
+
+ priv->can.do_set_bittime = ccan_set_bittime;
+ priv->can.do_get_state = ccan_get_state;
+ priv->can.do_set_mode = ccan_set_mode;
+
+ priv->dev = dev;
+ priv->tx_object = 0;
+
+ return dev;
+}
+EXPORT_SYMBOL(alloc_ccandev);
+
+void free_ccandev(struct net_device *dev)
+{
+ free_candev(dev);
+}
+EXPORT_SYMBOL(free_ccandev);
+
+int register_ccandev(struct net_device *dev)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+
+ ccan_set_mode(dev, CAN_MODE_START);
+
+ ccan_chip_config(dev);
+ INIT_DELAYED_WORK(&priv->work, do_statuspoll);
+
+ return register_netdev(dev);
+}
+EXPORT_SYMBOL(register_ccandev);
+
+void unregister_ccandev(struct net_device *dev)
+{
+ struct ccan_priv *priv = netdev_priv(dev);
+
+ ccan_set_mode(dev, CAN_MODE_START);
+
+ cancel_delayed_work(&priv->work);
+ flush_scheduled_work();
+
+ unregister_netdev(dev);
+}
+EXPORT_SYMBOL(unregister_ccandev);
+
+
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_AUTHOR("Simon Kallweit <simon.kallweit@intefo.ch>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("CAN port driver for C_CAN based chips");