linux-can.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4.1] can/sja1000: add support for PEAK-System PCMCIA card
@ 2012-02-13 14:18 Stephane Grosjean
  0 siblings, 0 replies; 9+ messages in thread
From: Stephane Grosjean @ 2012-02-13 14:18 UTC (permalink / raw)
  To: Oliver Hartkopp; +Cc: linux-can Mailing List, Stephane Grosjean

This patch adds the support of the PCAN-PC Card (PCMCIA) card from
PEAK-System Technik (http://www.peak-system.com). The PCAN-PC Card is
sja1000 based and exists in 1 or 2 channels.

Signed-off-by: Stephane Grosjean <s.grosjean@peak-system.com>
---
v4.1 changes:
- add card presence checking just before calling sja1000_interrupt()
- time_after() usage to handle jiffies wrapping
- !!onoff iusahe instead of (onoff) ? 1 : 0
- add some missing error msgs
- rename pcan_check_channel into pcan_channel_present
- redo _probe() error path right and update _free() functions accordingly

 drivers/net/can/sja1000/Kconfig       |    9 +
 drivers/net/can/sja1000/Makefile      |    1 +
 drivers/net/can/sja1000/peak_pcmcia.c |  753 +++++++++++++++++++++++++++++++++
 3 files changed, 763 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/can/sja1000/peak_pcmcia.c

diff --git a/drivers/net/can/sja1000/Kconfig b/drivers/net/can/sja1000/Kconfig
index 8116336..e6b0d6a 100644
--- a/drivers/net/can/sja1000/Kconfig
+++ b/drivers/net/can/sja1000/Kconfig
@@ -43,6 +43,15 @@ config CAN_EMS_PCI
 	  CPC-PCIe and CPC-104P cards from EMS Dr. Thomas Wuensche
 	  (http://www.ems-wuensche.de).
 
+config CAN_PEAK_PCMCIA
+	tristate "PEAK PCAN-PC Card"
+	depends on PCMCIA
+	---help---
+	  This driver is for the PCAN-PC Card PCMCIA adapter (1 or 2 channels)
+	  from PEAK-System Technik (http://www.peak-system.com).
+	  To compile this driver as a module, choose M here: the module will
+	  be called peak_pcmcia.
+
 config CAN_PEAK_PCI
 	tristate "PEAK PCAN-PCI/PCIe/miniPCI Cards"
 	depends on PCI
diff --git a/drivers/net/can/sja1000/Makefile b/drivers/net/can/sja1000/Makefile
index 0604f24..b3d05cb 100644
--- a/drivers/net/can/sja1000/Makefile
+++ b/drivers/net/can/sja1000/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_CAN_SJA1000_OF_PLATFORM) += sja1000_of_platform.o
 obj-$(CONFIG_CAN_EMS_PCMCIA) += ems_pcmcia.o
 obj-$(CONFIG_CAN_EMS_PCI) += ems_pci.o
 obj-$(CONFIG_CAN_KVASER_PCI) += kvaser_pci.o
+obj-$(CONFIG_CAN_PEAK_PCMCIA) += peak_pcmcia.o
 obj-$(CONFIG_CAN_PEAK_PCI) += peak_pci.o
 obj-$(CONFIG_CAN_PLX_PCI) += plx_pci.o
 obj-$(CONFIG_CAN_TSCAN1) += tscan1.o
diff --git a/drivers/net/can/sja1000/peak_pcmcia.c b/drivers/net/can/sja1000/peak_pcmcia.c
new file mode 100644
index 0000000..ec6bd9d
--- /dev/null
+++ b/drivers/net/can/sja1000/peak_pcmcia.c
@@ -0,0 +1,753 @@
+/*
+ * Copyright (C) 2010-2012 Stephane Grosjean <s.grosjean@peak-system.com>
+ *
+ * CAN driver for PEAK-System PCAN-PC Card
+ * Derived from the PCAN project file driver/src/pcan_pccard.c
+ * Copyright (C) 2006-2010 PEAK System-Technik GmbH
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+#include <linux/delay.h>
+#include <linux/timer.h>
+#include <linux/io.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include "sja1000.h"
+
+MODULE_AUTHOR("Stephane Grosjean <s.grosjean@peak-system.com>");
+MODULE_DESCRIPTION("CAN driver for PEAK-System PCAN-PC Cards");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("PEAK PCAN-PC Card");
+
+/* PEAK-System PCMCIA driver name */
+#define PCC_NAME		"peak_pcmcia"
+
+#define PCC_CHAN_MAX		2
+
+#define PCC_CAN_CLOCK		(16000000 / 2)
+
+#define PCC_MANF_ID		0x0377
+#define PCC_CARD_ID		0x0001
+
+#define PCC_CHAN_SIZE		0x20
+#define PCC_CHAN_OFF(c)		((c) * PCC_CHAN_SIZE)
+#define PCC_COMN_OFF		(PCC_CHAN_OFF(PCC_CHAN_MAX))
+#define PCC_COMN_SIZE		0x40
+
+/* common area registers */
+#define PCC_CCR			0x00
+#define PCC_CSR			0x02
+#define PCC_CPR			0x04
+#define PCC_SPI_DIR		0x06
+#define PCC_SPI_DOR		0x08
+#define PCC_SPI_ADR		0x0a
+#define PCC_SPI_IR		0x0c
+#define PCC_FW_MAJOR		0x10
+#define PCC_FW_MINOR		0x12
+
+/* CCR bits */
+#define PCC_CCR_CLK_16		0x00
+#define PCC_CCR_CLK_10		0x01
+#define PCC_CCR_CLK_21		0x02
+#define PCC_CCR_CLK_8		0x03
+#define PCC_CCR_CLK_MASK	PCC_CCR_CLK_8
+
+#define PCC_CCR_RST_CHAN(c)	(0x01 << ((c) + 2))
+#define PCC_CCR_RST_ALL		(PCC_CCR_RST_CHAN(0) | PCC_CCR_RST_CHAN(1))
+#define PCC_CCR_RST_MASK	PCC_CCR_RST_ALL
+
+/* led selection bits */
+#define PCC_LED(c)		(1 << (c))
+#define PCC_LED_ALL		(PCC_LED(0) | PCC_LED(1))
+
+/* led state value */
+#define PCC_LED_ON		0x00
+#define PCC_LED_FAST		0x01
+#define PCC_LED_SLOW		0x02
+#define PCC_LED_OFF		0x03
+
+#define PCC_CCR_LED_CHAN(s, c)	((s) << (((c) + 2) << 1))
+
+#define PCC_CCR_LED_ON_CHAN(c)		PCC_CCR_LED_CHAN(PCC_LED_ON, c)
+#define PCC_CCR_LED_FAST_CHAN(c)	PCC_CCR_LED_CHAN(PCC_LED_FAST, c)
+#define PCC_CCR_LED_SLOW_CHAN(c)	PCC_CCR_LED_CHAN(PCC_LED_SLOW, c)
+#define PCC_CCR_LED_OFF_CHAN(c)		PCC_CCR_LED_CHAN(PCC_LED_OFF, c)
+#define PCC_CCR_LED_MASK_CHAN(c)	PCC_CCR_LED_OFF_CHAN(c)
+#define PCC_CCR_LED_OFF_ALL		(PCC_CCR_LED_OFF_CHAN(0) | \
+					 PCC_CCR_LED_OFF_CHAN(1))
+#define PCC_CCR_LED_MASK		PCC_CCR_LED_OFF_ALL
+
+#define PCC_CCR_INIT	(PCC_CCR_CLK_16 | PCC_CCR_RST_ALL | PCC_CCR_LED_OFF_ALL)
+
+/* CSR bits */
+#define PCC_CSR_SPI_BUSY		0x04
+
+/* time waiting for SPI busy (prevent from infinite loop) */
+#define PCC_SPI_MAX_BUSY_WAIT_MS	3
+
+/* max count of reading the SPI status register waiting for a change */
+/* (prevent from infinite loop) */
+#define PCC_WRITE_MAX_LOOP		1000
+
+/* max nb of int handled by that isr in one shot (prevent from infinite loop) */
+#define PCC_ISR_MAX_LOOP		10
+
+/* EEPROM chip instruction set */
+/* note: EEPROM Read/Write instructions include A8 bit */
+#define PCC_EEP_WRITE(a)	(0x02 | (((a) & 0x100) >> 5))
+#define PCC_EEP_READ(a)		(0x03 | (((a) & 0x100) >> 5))
+#define PCC_EEP_WRDI		0x04	/* EEPROM Write Disable */
+#define PCC_EEP_RDSR		0x05	/* EEPROM Read Status Register */
+#define PCC_EEP_WREN		0x06	/* EEPROM Write Enable */
+
+/* EEPROM Status Register bits */
+#define PCC_EEP_SR_WEN		0x02	/* EEPROM SR Write Enable bit */
+#define PCC_EEP_SR_WIP		0x01	/* EEPROM SR Write In Progress bit */
+
+/*
+ * The board configuration is probably following:
+ * RX1 is connected to ground.
+ * TX1 is not connected.
+ * CLKO is not connected.
+ * Setting the OCR register to 0xDA is a good idea.
+ * This means normal output mode, push-pull and the correct polarity.
+ */
+#define PCC_OCR			(OCR_TX0_PUSHPULL | OCR_TX1_PUSHPULL)
+
+/*
+ * In the CDR register, you should set CBP to 1.
+ * You will probably also want to set the clock divider value to 7
+ * (meaning direct oscillator output) because the second SJA1000 chip
+ * is driven by the first one CLKOUT output.
+ */
+#define PCC_CDR			(CDR_CBP | CDR_CLKOUT_MASK)
+
+struct pcan_channel {
+	struct net_device *netdev;
+	unsigned long prev_rx_bytes;
+	unsigned long prev_tx_bytes;
+};
+
+/* PCAN-PC Card private structure */
+struct pcan_pccard {
+	struct pcmcia_device *pdev;
+	int chan_count;
+	struct pcan_channel channel[PCC_CHAN_MAX];
+	u8 ccr;
+	u8 fw_major;
+	u8 fw_minor;
+	void __iomem *ioport_addr;
+	struct timer_list led_timer;
+};
+
+static struct pcmcia_device_id pcan_table[] = {
+	PCMCIA_DEVICE_MANF_CARD(PCC_MANF_ID, PCC_CARD_ID),
+	PCMCIA_DEVICE_NULL,
+};
+
+MODULE_DEVICE_TABLE(pcmcia, pcan_table);
+
+static void pcan_set_leds(struct pcan_pccard *card, u8 mask, u8 state);
+
+/*
+ * start timer which controls leds state
+ */
+static void pcan_start_led_timer(struct pcan_pccard *card)
+{
+	if (!timer_pending(&card->led_timer))
+		mod_timer(&card->led_timer, jiffies + HZ);
+}
+
+/*
+ * stop the timer which controls leds state
+ */
+static void pcan_stop_led_timer(struct pcan_pccard *card)
+{
+	del_timer_sync(&card->led_timer);
+}
+
+/*
+ * read a sja1000 register
+ */
+static u8 pcan_read_canreg(const struct sja1000_priv *priv, int port)
+{
+	return ioread8(priv->reg_base + port);
+}
+
+/*
+ * write a sja1000 register
+ */
+static void pcan_write_canreg(const struct sja1000_priv *priv, int port, u8 v)
+{
+	struct pcan_pccard *card = priv->priv;
+	int c = (priv->reg_base - card->ioport_addr) / PCC_CHAN_SIZE;
+
+	/* sja1000 register changes control the leds state */
+	if (port == REG_MOD)
+		switch (v) {
+		case MOD_RM:
+			/* Reset Mode: set led on */
+			pcan_set_leds(card, PCC_LED(c), PCC_LED_ON);
+			break;
+		case 0x00:
+			/* Normal Mode: led slow blinking and start led timer */
+			pcan_set_leds(card, PCC_LED(c), PCC_LED_SLOW);
+			pcan_start_led_timer(card);
+			break;
+		default:
+			break;
+		}
+
+	iowrite8(v, priv->reg_base + port);
+}
+
+/*
+ * read a register from the common area
+ */
+static u8 pcan_read_reg(struct pcan_pccard *card, int port)
+{
+	return ioread8(card->ioport_addr + PCC_COMN_OFF + port);
+}
+
+/*
+ * write a register into the common area
+ */
+static void pcan_write_reg(struct pcan_pccard *card, int port, u8 v)
+{
+	/* cache ccr value */
+	if (port == PCC_CCR) {
+		if (card->ccr == v)
+			return;
+		card->ccr = v;
+	}
+
+	iowrite8(v, card->ioport_addr + PCC_COMN_OFF + port);
+}
+
+/*
+ * check whether the card is present by checking its fw version numbers
+ * against values read at probing time.
+ */
+static inline int pcan_pccard_present(struct pcan_pccard *card)
+{
+	return ((pcan_read_reg(card, PCC_FW_MAJOR) == card->fw_major) &&
+		(pcan_read_reg(card, PCC_FW_MINOR) == card->fw_minor));
+}
+
+/*
+ * wait for SPI engine while it is busy
+ */
+static int pcan_wait_spi_busy(struct pcan_pccard *card)
+{
+	unsigned long timeout = jiffies +
+				msecs_to_jiffies(PCC_SPI_MAX_BUSY_WAIT_MS) + 1;
+
+	/* be sure to read status at least once after sleeping */
+	while (pcan_read_reg(card, PCC_CSR) & PCC_CSR_SPI_BUSY) {
+		if (time_after(jiffies, timeout))
+			return -EBUSY;
+		schedule();
+	}
+
+	return 0;
+}
+
+/*
+ * write data in device eeprom
+ */
+static int pcan_write_eeprom(struct pcan_pccard *card, u16 addr, u8 v)
+{
+	u8 status;
+	int err, i;
+
+	/* write instruction enabling write */
+	pcan_write_reg(card, PCC_SPI_IR, PCC_EEP_WREN);
+	err = pcan_wait_spi_busy(card);
+	if (err)
+		goto we_spi_err;
+
+	/* wait until write enabled */
+	for (i = 0; i < PCC_WRITE_MAX_LOOP; i++) {
+		/* write instruction reading the status register */
+		pcan_write_reg(card, PCC_SPI_IR, PCC_EEP_RDSR);
+		err = pcan_wait_spi_busy(card);
+		if (err)
+			goto we_spi_err;
+
+		/* get status register value and check write enable bit */
+		status = pcan_read_reg(card, PCC_SPI_DIR);
+		if (status & PCC_EEP_SR_WEN)
+			break;
+	}
+
+	if (i >= PCC_WRITE_MAX_LOOP) {
+		dev_err(&card->pdev->dev,
+			"stop waiting to be allowed to write in eeprom\n");
+		return -EIO;
+	}
+
+	/* set address and data */
+	pcan_write_reg(card, PCC_SPI_ADR, addr & 0xff);
+	pcan_write_reg(card, PCC_SPI_DOR, v);
+
+	/*
+	 * write instruction with bit[3] set according to address value:
+	 * if addr refers to upper half of the memory array: bit[3] = 1
+	 */
+	pcan_write_reg(card, PCC_SPI_IR, PCC_EEP_WRITE(addr));
+	err = pcan_wait_spi_busy(card);
+	if (err)
+		goto we_spi_err;
+
+	/* wait while write in progress */
+	for (i = 0; i < PCC_WRITE_MAX_LOOP; i++) {
+		/* write instruction reading the status register */
+		pcan_write_reg(card, PCC_SPI_IR, PCC_EEP_RDSR);
+		err = pcan_wait_spi_busy(card);
+		if (err)
+			goto we_spi_err;
+
+		/* get status register value and check write in progress bit */
+		status = pcan_read_reg(card, PCC_SPI_DIR);
+		if (!(status & PCC_EEP_SR_WIP))
+			break;
+	}
+
+	if (i >= PCC_WRITE_MAX_LOOP) {
+		dev_err(&card->pdev->dev,
+			"stop waiting for write in eeprom to complete\n");
+		return -EIO;
+	}
+
+	/* write instruction disabling write */
+	pcan_write_reg(card, PCC_SPI_IR, PCC_EEP_WRDI);
+	err = pcan_wait_spi_busy(card);
+	if (err)
+		goto we_spi_err;
+
+	return 0;
+
+we_spi_err:
+	dev_err(&card->pdev->dev,
+		"stop waiting (spi engine always busy) err %d\n", err);
+
+	return err;
+}
+
+static void pcan_set_leds(struct pcan_pccard *card, u8 led_mask, u8 state)
+{
+	u8 ccr = card->ccr;
+	int i;
+
+	for (i = 0; i < card->chan_count; i++)
+		if (led_mask & PCC_LED(i)) {
+			/* clear corresponding led bits in ccr */
+			ccr &= ~PCC_CCR_LED_MASK_CHAN(i);
+			/* then set new bits */
+			ccr |= PCC_CCR_LED_CHAN(state, i);
+		}
+
+	/* real write only if something has changed in ccr */
+	pcan_write_reg(card, PCC_CCR, ccr);
+}
+
+/*
+ * enable/disable CAN connectors power
+ */
+static inline void pcan_set_can_power(struct pcan_pccard *card, int onoff)
+{
+	int err;
+
+	err = pcan_write_eeprom(card, 0, !!onoff);
+	if (err)
+		dev_err(&card->pdev->dev,
+			"failed setting power %s to can connectors (err %d)\n",
+			(onoff) ? "on" : "off", err);
+}
+
+/*
+ * set leds state according to channel activity
+ */
+static void pcan_led_timer(unsigned long arg)
+{
+	struct pcan_pccard *card = (struct pcan_pccard *)arg;
+	struct net_device *netdev;
+	int i, up_count = 0;
+	u8 ccr;
+
+	ccr = card->ccr;
+	for (i = 0; i < card->chan_count; i++) {
+		/* default is: not configured */
+		ccr &= ~PCC_CCR_LED_MASK_CHAN(i);
+		ccr |= PCC_CCR_LED_ON_CHAN(i);
+
+		netdev = card->channel[i].netdev;
+		if (!netdev || !(netdev->flags & IFF_UP))
+			continue;
+
+		up_count++;
+
+		/* no activity (but configured) */
+		ccr &= ~PCC_CCR_LED_MASK_CHAN(i);
+		ccr |= PCC_CCR_LED_SLOW_CHAN(i);
+
+		/* if bytes counters changed, set fast blinking led */
+		if (netdev->stats.rx_bytes != card->channel[i].prev_rx_bytes) {
+			card->channel[i].prev_rx_bytes = netdev->stats.rx_bytes;
+			ccr &= ~PCC_CCR_LED_MASK_CHAN(i);
+			ccr |= PCC_CCR_LED_FAST_CHAN(i);
+		}
+		if (netdev->stats.tx_bytes != card->channel[i].prev_tx_bytes) {
+			card->channel[i].prev_tx_bytes = netdev->stats.tx_bytes;
+			ccr &= ~PCC_CCR_LED_MASK_CHAN(i);
+			ccr |= PCC_CCR_LED_FAST_CHAN(i);
+		}
+	}
+
+	/* write the new leds state */
+	pcan_write_reg(card, PCC_CCR, ccr);
+
+	/* restart timer (except if no more configured channels) */
+	if (up_count)
+		mod_timer(&card->led_timer, jiffies + HZ);
+}
+
+/*
+ * interrupt service routine
+ */
+static irqreturn_t pcan_isr(int irq, void *dev_id)
+{
+	struct pcan_pccard *card = dev_id;
+	int irq_handled;
+
+	/* prevent from infinite loop */
+	for (irq_handled = 0; irq_handled < PCC_ISR_MAX_LOOP; irq_handled++) {
+		/* handle shared interrupt and next loop */
+		int nothing_to_handle = 1;
+		int i;
+
+		/* check interrupt for each channel */
+		for (i = 0; i < card->chan_count; i++) {
+			struct net_device *netdev;
+
+			/*
+			 * check whether the card is present before calling
+			 * sja1000_interrupt() to speed up hotplug detection
+			 */
+			if (!pcan_pccard_present(card)) {
+				/* card unplugged during isr */
+				return IRQ_NONE;
+			}
+
+			/*
+			 * should check whether all or SJA1000_MAX_IRQ
+			 * interrupts have been handled: loop again to be sure.
+			 */
+			netdev = card->channel[i].netdev;
+			if (netdev &&
+			    sja1000_interrupt(irq, netdev) == IRQ_HANDLED)
+				nothing_to_handle = 0;
+		}
+
+		if (nothing_to_handle)
+			break;
+	}
+
+	return (irq_handled) ? IRQ_HANDLED : IRQ_NONE;
+}
+
+/*
+ * free all resources used by the channels and switch off leds and can power
+ */
+static void pcan_free_channels(struct pcan_pccard *card)
+{
+	int i;
+	u8 led_mask = 0;
+
+	for (i = 0; i < card->chan_count; i++) {
+		struct net_device *netdev;
+		char name[IFNAMSIZ];
+
+		led_mask |= PCC_LED(i);
+
+		netdev = card->channel[i].netdev;
+		if (!netdev)
+			continue;
+
+		strncpy(name, netdev->name, IFNAMSIZ);
+
+		unregister_sja1000dev(netdev);
+
+		free_sja1000dev(netdev);
+
+		dev_info(&card->pdev->dev, "%s removed\n", name);
+	}
+
+	/* do it only if device not removed */
+	if (pcan_pccard_present(card)) {
+		pcan_set_leds(card, led_mask, PCC_LED_OFF);
+		pcan_set_can_power(card, 0);
+	}
+}
+
+/*
+ * check if a CAN controller is present at the specified location
+ */
+static inline int pcan_channel_present(struct sja1000_priv *priv)
+{
+	/* make sure SJA1000 is in reset mode */
+	pcan_write_canreg(priv, REG_MOD, 1);
+	pcan_write_canreg(priv, REG_CDR, CDR_PELICAN);
+
+	/* read reset-values */
+	if (pcan_read_canreg(priv, REG_CDR) == CDR_PELICAN)
+		return 1;
+
+	return 0;
+}
+
+static int pcan_add_channels(struct pcan_pccard *card)
+{
+	struct pcmcia_device *pdev = card->pdev;
+	int i, err = 0;
+	u8 ccr = PCC_CCR_INIT;
+
+	/* init common registers (reset channels and leds off) */
+	card->ccr = ~ccr;
+	pcan_write_reg(card, PCC_CCR, ccr);
+
+	/* wait 2ms before unresetting channels */
+	mdelay(2);
+
+	ccr &= ~PCC_CCR_RST_ALL;
+	pcan_write_reg(card, PCC_CCR, ccr);
+
+	/* create one network device per channel detected */
+	for (i = 0; i < ARRAY_SIZE(card->channel); i++) {
+		struct net_device *netdev;
+		struct sja1000_priv *priv;
+
+		netdev = alloc_sja1000dev(0);
+		if (!netdev) {
+			err = -ENOMEM;
+			break;
+		}
+
+		/* update linkages */
+		priv = netdev_priv(netdev);
+		priv->priv = card;
+		SET_NETDEV_DEV(netdev, &pdev->dev);
+
+		priv->irq_flags = IRQF_SHARED;
+		netdev->irq = pdev->irq;
+		priv->reg_base = card->ioport_addr + PCC_CHAN_OFF(i);
+
+		/* check if channel is present */
+		if (!pcan_channel_present(priv)) {
+			dev_err(&pdev->dev, "channel %d not present\n", i);
+			free_sja1000dev(netdev);
+			continue;
+		}
+
+		priv->read_reg  = pcan_read_canreg;
+		priv->write_reg = pcan_write_canreg;
+		priv->can.clock.freq = PCC_CAN_CLOCK;
+		priv->ocr = PCC_OCR;
+		priv->cdr = PCC_CDR;
+
+		/* Neither a slave device distributes the clock */
+		if (i > 0)
+			priv->cdr |= CDR_CLK_OFF;
+
+		priv->flags |= SJA1000_CUSTOM_IRQ_HANDLER;
+
+		/* register SJA1000 device */
+		err = register_sja1000dev(netdev);
+		if (err) {
+			free_sja1000dev(netdev);
+			continue;
+		}
+
+		card->channel[i].netdev = netdev;
+		card->chan_count++;
+
+		/* set corresponding led on in the new ccr */
+		ccr &= ~PCC_CCR_LED_OFF_CHAN(i);
+
+		dev_info(&pdev->dev,
+			"%s on channel %d at 0x%p irq %d\n",
+			netdev->name, i, priv->reg_base, pdev->irq);
+	}
+
+	/* write new ccr (change leds state) */
+	pcan_write_reg(card, PCC_CCR, ccr);
+
+	return err;
+}
+
+static int pcan_conf_check(struct pcmcia_device *pdev, void *priv_data)
+{
+	pdev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	pdev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; /* only */
+	pdev->io_lines = 10;
+
+	/* This reserves IO space but doesn't actually enable it */
+	return pcmcia_request_io(pdev);
+}
+
+/*
+ * free all resources used by the device
+ */
+static void pcan_free(struct pcmcia_device *pdev)
+{
+	struct pcan_pccard *card = pdev->priv;
+
+	if (!card)
+		return;
+
+	free_irq(pdev->irq, card);
+	pcan_stop_led_timer(card);
+
+	pcan_free_channels(card);
+
+	ioport_unmap(card->ioport_addr);
+
+	kfree(card);
+	pdev->priv = NULL;
+}
+
+/*
+ * setup PCMCIA socket and probe for PEAK-System PC-CARD
+ */
+static int __devinit pcan_probe(struct pcmcia_device *pdev)
+{
+	struct pcan_pccard *card;
+	int err;
+
+	pdev->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
+
+	err = pcmcia_loop_config(pdev, pcan_conf_check, NULL);
+	if (err) {
+		dev_err(&pdev->dev, "pcmcia_loop_config() error %d\n", err);
+		goto probe_err_1;
+	}
+
+	if (!pdev->irq) {
+		dev_err(&pdev->dev, "no irq assigned\n");
+		err = -ENODEV;
+		goto probe_err_1;
+	}
+
+	err = pcmcia_enable_device(pdev);
+	if (err) {
+		dev_err(&pdev->dev, "pcmcia_enable_device failed err=%d\n",
+			err);
+		goto probe_err_1;
+	}
+
+	card = kzalloc(sizeof(struct pcan_pccard), GFP_KERNEL);
+	if (!card) {
+		dev_err(&pdev->dev, "couldn't allocate card memory\n");
+		err = -ENOMEM;
+		goto probe_err_2;
+	}
+
+	card->pdev = pdev;
+	pdev->priv = card;
+
+	/* sja1000 api uses iomem */
+	card->ioport_addr = ioport_map(pdev->resource[0]->start,
+					resource_size(pdev->resource[0]));
+	if (!card->ioport_addr) {
+		dev_err(&pdev->dev, "couldn't map io port into io memory\n");
+		err = -ENOMEM;
+		goto probe_err_3;
+	}
+	card->fw_major = pcan_read_reg(card, PCC_FW_MAJOR);
+	card->fw_minor = pcan_read_reg(card, PCC_FW_MINOR);
+
+	/* display board name and firware version */
+	dev_info(&pdev->dev, "PEAK-System pcmcia card %s fw %d.%d\n",
+		pdev->prod_id[1] ? pdev->prod_id[1] : "PCAN-PC Card",
+		card->fw_major, card->fw_minor);
+
+	/* detect available channels */
+	pcan_add_channels(card);
+	if (!card->chan_count)
+		goto probe_err_4;
+
+	/* init the timer which controls the leds */
+	init_timer(&card->led_timer);
+	card->led_timer.function = pcan_led_timer;
+	card->led_timer.data = (unsigned long)card;
+
+	/* request the given irq */
+	err = request_irq(pdev->irq, &pcan_isr, IRQF_SHARED, PCC_NAME, card);
+	if (err) {
+		dev_err(&pdev->dev, "couldn't request irq%d\n", pdev->irq);
+		goto probe_err_5;
+	}
+
+	/* power on the connectors */
+	pcan_set_can_power(card, 1);
+
+	return 0;
+
+probe_err_5:
+	/* unregister can devices from network */
+	pcan_free_channels(card);
+
+probe_err_4:
+	ioport_unmap(card->ioport_addr);
+
+probe_err_3:
+	kfree(card);
+	pdev->priv = NULL;
+
+probe_err_2:
+	pcmcia_disable_device(pdev);
+
+probe_err_1:
+	return err;
+}
+
+/*
+ * release claimed resources
+ */
+static void pcan_remove(struct pcmcia_device *pdev)
+{
+	pcan_free(pdev);
+	pcmcia_disable_device(pdev);
+}
+
+static struct pcmcia_driver pcan_driver = {
+	.name = PCC_NAME,
+	.probe = pcan_probe,
+	.remove = pcan_remove,
+	.id_table = pcan_table,
+};
+
+static int __init pcan_init(void)
+{
+	return pcmcia_register_driver(&pcan_driver);
+}
+module_init(pcan_init);
+
+static void __exit pcan_exit(void)
+{
+	pcmcia_unregister_driver(&pcan_driver);
+}
+module_exit(pcan_exit);
-- 
1.7.5.4


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v4.1] can/sja1000: add support for PEAK-System PCMCIA card
@ 2012-02-16 10:10 Stephane Grosjean
  2012-02-17  8:24 ` Dominik Brodowski
  0 siblings, 1 reply; 9+ messages in thread
From: Stephane Grosjean @ 2012-02-16 10:10 UTC (permalink / raw)
  To: Oliver Hartkopp
  Cc: linux-can Mailing List, linux pcmcia Mailing List,
	Stephane Grosjean

This patch adds the support of the PCAN-PC Card (PCMCIA) card from
PEAK-System Technik (http://www.peak-system.com). The PCAN-PC Card is
sja1000 based and exists in 1 or 2 channels.

Signed-off-by: Stephane Grosjean <s.grosjean@peak-system.com>
---
v4.1 changes:
- add card presence checking just before calling sja1000_interrupt()
- time_after() usage to handle jiffies wrapping
- !!onoff usage instead of (onoff) ? 1 : 0
- add some missing error msgs
- rename pcan_check_channel into pcan_channel_present
- redo _probe() error path right and update _free() functions accordingly

 drivers/net/can/sja1000/Kconfig       |    9 +
 drivers/net/can/sja1000/Makefile      |    1 +
 drivers/net/can/sja1000/peak_pcmcia.c |  753 +++++++++++++++++++++++++++++++++
 3 files changed, 763 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/can/sja1000/peak_pcmcia.c

diff --git a/drivers/net/can/sja1000/Kconfig b/drivers/net/can/sja1000/Kconfig
index 8116336..e6b0d6a 100644
--- a/drivers/net/can/sja1000/Kconfig
+++ b/drivers/net/can/sja1000/Kconfig
@@ -43,6 +43,15 @@ config CAN_EMS_PCI
 	  CPC-PCIe and CPC-104P cards from EMS Dr. Thomas Wuensche
 	  (http://www.ems-wuensche.de).
 
+config CAN_PEAK_PCMCIA
+	tristate "PEAK PCAN-PC Card"
+	depends on PCMCIA
+	---help---
+	  This driver is for the PCAN-PC Card PCMCIA adapter (1 or 2 channels)
+	  from PEAK-System Technik (http://www.peak-system.com).
+	  To compile this driver as a module, choose M here: the module will
+	  be called peak_pcmcia.
+
 config CAN_PEAK_PCI
 	tristate "PEAK PCAN-PCI/PCIe/miniPCI Cards"
 	depends on PCI
diff --git a/drivers/net/can/sja1000/Makefile b/drivers/net/can/sja1000/Makefile
index 0604f24..b3d05cb 100644
--- a/drivers/net/can/sja1000/Makefile
+++ b/drivers/net/can/sja1000/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_CAN_SJA1000_OF_PLATFORM) += sja1000_of_platform.o
 obj-$(CONFIG_CAN_EMS_PCMCIA) += ems_pcmcia.o
 obj-$(CONFIG_CAN_EMS_PCI) += ems_pci.o
 obj-$(CONFIG_CAN_KVASER_PCI) += kvaser_pci.o
+obj-$(CONFIG_CAN_PEAK_PCMCIA) += peak_pcmcia.o
 obj-$(CONFIG_CAN_PEAK_PCI) += peak_pci.o
 obj-$(CONFIG_CAN_PLX_PCI) += plx_pci.o
 obj-$(CONFIG_CAN_TSCAN1) += tscan1.o
diff --git a/drivers/net/can/sja1000/peak_pcmcia.c b/drivers/net/can/sja1000/peak_pcmcia.c
new file mode 100644
index 0000000..ec6bd9d
--- /dev/null
+++ b/drivers/net/can/sja1000/peak_pcmcia.c
@@ -0,0 +1,753 @@
+/*
+ * Copyright (C) 2010-2012 Stephane Grosjean <s.grosjean@peak-system.com>
+ *
+ * CAN driver for PEAK-System PCAN-PC Card
+ * Derived from the PCAN project file driver/src/pcan_pccard.c
+ * Copyright (C) 2006-2010 PEAK System-Technik GmbH
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+#include <linux/delay.h>
+#include <linux/timer.h>
+#include <linux/io.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include "sja1000.h"
+
+MODULE_AUTHOR("Stephane Grosjean <s.grosjean@peak-system.com>");
+MODULE_DESCRIPTION("CAN driver for PEAK-System PCAN-PC Cards");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("PEAK PCAN-PC Card");
+
+/* PEAK-System PCMCIA driver name */
+#define PCC_NAME		"peak_pcmcia"
+
+#define PCC_CHAN_MAX		2
+
+#define PCC_CAN_CLOCK		(16000000 / 2)
+
+#define PCC_MANF_ID		0x0377
+#define PCC_CARD_ID		0x0001
+
+#define PCC_CHAN_SIZE		0x20
+#define PCC_CHAN_OFF(c)		((c) * PCC_CHAN_SIZE)
+#define PCC_COMN_OFF		(PCC_CHAN_OFF(PCC_CHAN_MAX))
+#define PCC_COMN_SIZE		0x40
+
+/* common area registers */
+#define PCC_CCR			0x00
+#define PCC_CSR			0x02
+#define PCC_CPR			0x04
+#define PCC_SPI_DIR		0x06
+#define PCC_SPI_DOR		0x08
+#define PCC_SPI_ADR		0x0a
+#define PCC_SPI_IR		0x0c
+#define PCC_FW_MAJOR		0x10
+#define PCC_FW_MINOR		0x12
+
+/* CCR bits */
+#define PCC_CCR_CLK_16		0x00
+#define PCC_CCR_CLK_10		0x01
+#define PCC_CCR_CLK_21		0x02
+#define PCC_CCR_CLK_8		0x03
+#define PCC_CCR_CLK_MASK	PCC_CCR_CLK_8
+
+#define PCC_CCR_RST_CHAN(c)	(0x01 << ((c) + 2))
+#define PCC_CCR_RST_ALL		(PCC_CCR_RST_CHAN(0) | PCC_CCR_RST_CHAN(1))
+#define PCC_CCR_RST_MASK	PCC_CCR_RST_ALL
+
+/* led selection bits */
+#define PCC_LED(c)		(1 << (c))
+#define PCC_LED_ALL		(PCC_LED(0) | PCC_LED(1))
+
+/* led state value */
+#define PCC_LED_ON		0x00
+#define PCC_LED_FAST		0x01
+#define PCC_LED_SLOW		0x02
+#define PCC_LED_OFF		0x03
+
+#define PCC_CCR_LED_CHAN(s, c)	((s) << (((c) + 2) << 1))
+
+#define PCC_CCR_LED_ON_CHAN(c)		PCC_CCR_LED_CHAN(PCC_LED_ON, c)
+#define PCC_CCR_LED_FAST_CHAN(c)	PCC_CCR_LED_CHAN(PCC_LED_FAST, c)
+#define PCC_CCR_LED_SLOW_CHAN(c)	PCC_CCR_LED_CHAN(PCC_LED_SLOW, c)
+#define PCC_CCR_LED_OFF_CHAN(c)		PCC_CCR_LED_CHAN(PCC_LED_OFF, c)
+#define PCC_CCR_LED_MASK_CHAN(c)	PCC_CCR_LED_OFF_CHAN(c)
+#define PCC_CCR_LED_OFF_ALL		(PCC_CCR_LED_OFF_CHAN(0) | \
+					 PCC_CCR_LED_OFF_CHAN(1))
+#define PCC_CCR_LED_MASK		PCC_CCR_LED_OFF_ALL
+
+#define PCC_CCR_INIT	(PCC_CCR_CLK_16 | PCC_CCR_RST_ALL | PCC_CCR_LED_OFF_ALL)
+
+/* CSR bits */
+#define PCC_CSR_SPI_BUSY		0x04
+
+/* time waiting for SPI busy (prevent from infinite loop) */
+#define PCC_SPI_MAX_BUSY_WAIT_MS	3
+
+/* max count of reading the SPI status register waiting for a change */
+/* (prevent from infinite loop) */
+#define PCC_WRITE_MAX_LOOP		1000
+
+/* max nb of int handled by that isr in one shot (prevent from infinite loop) */
+#define PCC_ISR_MAX_LOOP		10
+
+/* EEPROM chip instruction set */
+/* note: EEPROM Read/Write instructions include A8 bit */
+#define PCC_EEP_WRITE(a)	(0x02 | (((a) & 0x100) >> 5))
+#define PCC_EEP_READ(a)		(0x03 | (((a) & 0x100) >> 5))
+#define PCC_EEP_WRDI		0x04	/* EEPROM Write Disable */
+#define PCC_EEP_RDSR		0x05	/* EEPROM Read Status Register */
+#define PCC_EEP_WREN		0x06	/* EEPROM Write Enable */
+
+/* EEPROM Status Register bits */
+#define PCC_EEP_SR_WEN		0x02	/* EEPROM SR Write Enable bit */
+#define PCC_EEP_SR_WIP		0x01	/* EEPROM SR Write In Progress bit */
+
+/*
+ * The board configuration is probably following:
+ * RX1 is connected to ground.
+ * TX1 is not connected.
+ * CLKO is not connected.
+ * Setting the OCR register to 0xDA is a good idea.
+ * This means normal output mode, push-pull and the correct polarity.
+ */
+#define PCC_OCR			(OCR_TX0_PUSHPULL | OCR_TX1_PUSHPULL)
+
+/*
+ * In the CDR register, you should set CBP to 1.
+ * You will probably also want to set the clock divider value to 7
+ * (meaning direct oscillator output) because the second SJA1000 chip
+ * is driven by the first one CLKOUT output.
+ */
+#define PCC_CDR			(CDR_CBP | CDR_CLKOUT_MASK)
+
+struct pcan_channel {
+	struct net_device *netdev;
+	unsigned long prev_rx_bytes;
+	unsigned long prev_tx_bytes;
+};
+
+/* PCAN-PC Card private structure */
+struct pcan_pccard {
+	struct pcmcia_device *pdev;
+	int chan_count;
+	struct pcan_channel channel[PCC_CHAN_MAX];
+	u8 ccr;
+	u8 fw_major;
+	u8 fw_minor;
+	void __iomem *ioport_addr;
+	struct timer_list led_timer;
+};
+
+static struct pcmcia_device_id pcan_table[] = {
+	PCMCIA_DEVICE_MANF_CARD(PCC_MANF_ID, PCC_CARD_ID),
+	PCMCIA_DEVICE_NULL,
+};
+
+MODULE_DEVICE_TABLE(pcmcia, pcan_table);
+
+static void pcan_set_leds(struct pcan_pccard *card, u8 mask, u8 state);
+
+/*
+ * start timer which controls leds state
+ */
+static void pcan_start_led_timer(struct pcan_pccard *card)
+{
+	if (!timer_pending(&card->led_timer))
+		mod_timer(&card->led_timer, jiffies + HZ);
+}
+
+/*
+ * stop the timer which controls leds state
+ */
+static void pcan_stop_led_timer(struct pcan_pccard *card)
+{
+	del_timer_sync(&card->led_timer);
+}
+
+/*
+ * read a sja1000 register
+ */
+static u8 pcan_read_canreg(const struct sja1000_priv *priv, int port)
+{
+	return ioread8(priv->reg_base + port);
+}
+
+/*
+ * write a sja1000 register
+ */
+static void pcan_write_canreg(const struct sja1000_priv *priv, int port, u8 v)
+{
+	struct pcan_pccard *card = priv->priv;
+	int c = (priv->reg_base - card->ioport_addr) / PCC_CHAN_SIZE;
+
+	/* sja1000 register changes control the leds state */
+	if (port == REG_MOD)
+		switch (v) {
+		case MOD_RM:
+			/* Reset Mode: set led on */
+			pcan_set_leds(card, PCC_LED(c), PCC_LED_ON);
+			break;
+		case 0x00:
+			/* Normal Mode: led slow blinking and start led timer */
+			pcan_set_leds(card, PCC_LED(c), PCC_LED_SLOW);
+			pcan_start_led_timer(card);
+			break;
+		default:
+			break;
+		}
+
+	iowrite8(v, priv->reg_base + port);
+}
+
+/*
+ * read a register from the common area
+ */
+static u8 pcan_read_reg(struct pcan_pccard *card, int port)
+{
+	return ioread8(card->ioport_addr + PCC_COMN_OFF + port);
+}
+
+/*
+ * write a register into the common area
+ */
+static void pcan_write_reg(struct pcan_pccard *card, int port, u8 v)
+{
+	/* cache ccr value */
+	if (port == PCC_CCR) {
+		if (card->ccr == v)
+			return;
+		card->ccr = v;
+	}
+
+	iowrite8(v, card->ioport_addr + PCC_COMN_OFF + port);
+}
+
+/*
+ * check whether the card is present by checking its fw version numbers
+ * against values read at probing time.
+ */
+static inline int pcan_pccard_present(struct pcan_pccard *card)
+{
+	return ((pcan_read_reg(card, PCC_FW_MAJOR) == card->fw_major) &&
+		(pcan_read_reg(card, PCC_FW_MINOR) == card->fw_minor));
+}
+
+/*
+ * wait for SPI engine while it is busy
+ */
+static int pcan_wait_spi_busy(struct pcan_pccard *card)
+{
+	unsigned long timeout = jiffies +
+				msecs_to_jiffies(PCC_SPI_MAX_BUSY_WAIT_MS) + 1;
+
+	/* be sure to read status at least once after sleeping */
+	while (pcan_read_reg(card, PCC_CSR) & PCC_CSR_SPI_BUSY) {
+		if (time_after(jiffies, timeout))
+			return -EBUSY;
+		schedule();
+	}
+
+	return 0;
+}
+
+/*
+ * write data in device eeprom
+ */
+static int pcan_write_eeprom(struct pcan_pccard *card, u16 addr, u8 v)
+{
+	u8 status;
+	int err, i;
+
+	/* write instruction enabling write */
+	pcan_write_reg(card, PCC_SPI_IR, PCC_EEP_WREN);
+	err = pcan_wait_spi_busy(card);
+	if (err)
+		goto we_spi_err;
+
+	/* wait until write enabled */
+	for (i = 0; i < PCC_WRITE_MAX_LOOP; i++) {
+		/* write instruction reading the status register */
+		pcan_write_reg(card, PCC_SPI_IR, PCC_EEP_RDSR);
+		err = pcan_wait_spi_busy(card);
+		if (err)
+			goto we_spi_err;
+
+		/* get status register value and check write enable bit */
+		status = pcan_read_reg(card, PCC_SPI_DIR);
+		if (status & PCC_EEP_SR_WEN)
+			break;
+	}
+
+	if (i >= PCC_WRITE_MAX_LOOP) {
+		dev_err(&card->pdev->dev,
+			"stop waiting to be allowed to write in eeprom\n");
+		return -EIO;
+	}
+
+	/* set address and data */
+	pcan_write_reg(card, PCC_SPI_ADR, addr & 0xff);
+	pcan_write_reg(card, PCC_SPI_DOR, v);
+
+	/*
+	 * write instruction with bit[3] set according to address value:
+	 * if addr refers to upper half of the memory array: bit[3] = 1
+	 */
+	pcan_write_reg(card, PCC_SPI_IR, PCC_EEP_WRITE(addr));
+	err = pcan_wait_spi_busy(card);
+	if (err)
+		goto we_spi_err;
+
+	/* wait while write in progress */
+	for (i = 0; i < PCC_WRITE_MAX_LOOP; i++) {
+		/* write instruction reading the status register */
+		pcan_write_reg(card, PCC_SPI_IR, PCC_EEP_RDSR);
+		err = pcan_wait_spi_busy(card);
+		if (err)
+			goto we_spi_err;
+
+		/* get status register value and check write in progress bit */
+		status = pcan_read_reg(card, PCC_SPI_DIR);
+		if (!(status & PCC_EEP_SR_WIP))
+			break;
+	}
+
+	if (i >= PCC_WRITE_MAX_LOOP) {
+		dev_err(&card->pdev->dev,
+			"stop waiting for write in eeprom to complete\n");
+		return -EIO;
+	}
+
+	/* write instruction disabling write */
+	pcan_write_reg(card, PCC_SPI_IR, PCC_EEP_WRDI);
+	err = pcan_wait_spi_busy(card);
+	if (err)
+		goto we_spi_err;
+
+	return 0;
+
+we_spi_err:
+	dev_err(&card->pdev->dev,
+		"stop waiting (spi engine always busy) err %d\n", err);
+
+	return err;
+}
+
+static void pcan_set_leds(struct pcan_pccard *card, u8 led_mask, u8 state)
+{
+	u8 ccr = card->ccr;
+	int i;
+
+	for (i = 0; i < card->chan_count; i++)
+		if (led_mask & PCC_LED(i)) {
+			/* clear corresponding led bits in ccr */
+			ccr &= ~PCC_CCR_LED_MASK_CHAN(i);
+			/* then set new bits */
+			ccr |= PCC_CCR_LED_CHAN(state, i);
+		}
+
+	/* real write only if something has changed in ccr */
+	pcan_write_reg(card, PCC_CCR, ccr);
+}
+
+/*
+ * enable/disable CAN connectors power
+ */
+static inline void pcan_set_can_power(struct pcan_pccard *card, int onoff)
+{
+	int err;
+
+	err = pcan_write_eeprom(card, 0, !!onoff);
+	if (err)
+		dev_err(&card->pdev->dev,
+			"failed setting power %s to can connectors (err %d)\n",
+			(onoff) ? "on" : "off", err);
+}
+
+/*
+ * set leds state according to channel activity
+ */
+static void pcan_led_timer(unsigned long arg)
+{
+	struct pcan_pccard *card = (struct pcan_pccard *)arg;
+	struct net_device *netdev;
+	int i, up_count = 0;
+	u8 ccr;
+
+	ccr = card->ccr;
+	for (i = 0; i < card->chan_count; i++) {
+		/* default is: not configured */
+		ccr &= ~PCC_CCR_LED_MASK_CHAN(i);
+		ccr |= PCC_CCR_LED_ON_CHAN(i);
+
+		netdev = card->channel[i].netdev;
+		if (!netdev || !(netdev->flags & IFF_UP))
+			continue;
+
+		up_count++;
+
+		/* no activity (but configured) */
+		ccr &= ~PCC_CCR_LED_MASK_CHAN(i);
+		ccr |= PCC_CCR_LED_SLOW_CHAN(i);
+
+		/* if bytes counters changed, set fast blinking led */
+		if (netdev->stats.rx_bytes != card->channel[i].prev_rx_bytes) {
+			card->channel[i].prev_rx_bytes = netdev->stats.rx_bytes;
+			ccr &= ~PCC_CCR_LED_MASK_CHAN(i);
+			ccr |= PCC_CCR_LED_FAST_CHAN(i);
+		}
+		if (netdev->stats.tx_bytes != card->channel[i].prev_tx_bytes) {
+			card->channel[i].prev_tx_bytes = netdev->stats.tx_bytes;
+			ccr &= ~PCC_CCR_LED_MASK_CHAN(i);
+			ccr |= PCC_CCR_LED_FAST_CHAN(i);
+		}
+	}
+
+	/* write the new leds state */
+	pcan_write_reg(card, PCC_CCR, ccr);
+
+	/* restart timer (except if no more configured channels) */
+	if (up_count)
+		mod_timer(&card->led_timer, jiffies + HZ);
+}
+
+/*
+ * interrupt service routine
+ */
+static irqreturn_t pcan_isr(int irq, void *dev_id)
+{
+	struct pcan_pccard *card = dev_id;
+	int irq_handled;
+
+	/* prevent from infinite loop */
+	for (irq_handled = 0; irq_handled < PCC_ISR_MAX_LOOP; irq_handled++) {
+		/* handle shared interrupt and next loop */
+		int nothing_to_handle = 1;
+		int i;
+
+		/* check interrupt for each channel */
+		for (i = 0; i < card->chan_count; i++) {
+			struct net_device *netdev;
+
+			/*
+			 * check whether the card is present before calling
+			 * sja1000_interrupt() to speed up hotplug detection
+			 */
+			if (!pcan_pccard_present(card)) {
+				/* card unplugged during isr */
+				return IRQ_NONE;
+			}
+
+			/*
+			 * should check whether all or SJA1000_MAX_IRQ
+			 * interrupts have been handled: loop again to be sure.
+			 */
+			netdev = card->channel[i].netdev;
+			if (netdev &&
+			    sja1000_interrupt(irq, netdev) == IRQ_HANDLED)
+				nothing_to_handle = 0;
+		}
+
+		if (nothing_to_handle)
+			break;
+	}
+
+	return (irq_handled) ? IRQ_HANDLED : IRQ_NONE;
+}
+
+/*
+ * free all resources used by the channels and switch off leds and can power
+ */
+static void pcan_free_channels(struct pcan_pccard *card)
+{
+	int i;
+	u8 led_mask = 0;
+
+	for (i = 0; i < card->chan_count; i++) {
+		struct net_device *netdev;
+		char name[IFNAMSIZ];
+
+		led_mask |= PCC_LED(i);
+
+		netdev = card->channel[i].netdev;
+		if (!netdev)
+			continue;
+
+		strncpy(name, netdev->name, IFNAMSIZ);
+
+		unregister_sja1000dev(netdev);
+
+		free_sja1000dev(netdev);
+
+		dev_info(&card->pdev->dev, "%s removed\n", name);
+	}
+
+	/* do it only if device not removed */
+	if (pcan_pccard_present(card)) {
+		pcan_set_leds(card, led_mask, PCC_LED_OFF);
+		pcan_set_can_power(card, 0);
+	}
+}
+
+/*
+ * check if a CAN controller is present at the specified location
+ */
+static inline int pcan_channel_present(struct sja1000_priv *priv)
+{
+	/* make sure SJA1000 is in reset mode */
+	pcan_write_canreg(priv, REG_MOD, 1);
+	pcan_write_canreg(priv, REG_CDR, CDR_PELICAN);
+
+	/* read reset-values */
+	if (pcan_read_canreg(priv, REG_CDR) == CDR_PELICAN)
+		return 1;
+
+	return 0;
+}
+
+static int pcan_add_channels(struct pcan_pccard *card)
+{
+	struct pcmcia_device *pdev = card->pdev;
+	int i, err = 0;
+	u8 ccr = PCC_CCR_INIT;
+
+	/* init common registers (reset channels and leds off) */
+	card->ccr = ~ccr;
+	pcan_write_reg(card, PCC_CCR, ccr);
+
+	/* wait 2ms before unresetting channels */
+	mdelay(2);
+
+	ccr &= ~PCC_CCR_RST_ALL;
+	pcan_write_reg(card, PCC_CCR, ccr);
+
+	/* create one network device per channel detected */
+	for (i = 0; i < ARRAY_SIZE(card->channel); i++) {
+		struct net_device *netdev;
+		struct sja1000_priv *priv;
+
+		netdev = alloc_sja1000dev(0);
+		if (!netdev) {
+			err = -ENOMEM;
+			break;
+		}
+
+		/* update linkages */
+		priv = netdev_priv(netdev);
+		priv->priv = card;
+		SET_NETDEV_DEV(netdev, &pdev->dev);
+
+		priv->irq_flags = IRQF_SHARED;
+		netdev->irq = pdev->irq;
+		priv->reg_base = card->ioport_addr + PCC_CHAN_OFF(i);
+
+		/* check if channel is present */
+		if (!pcan_channel_present(priv)) {
+			dev_err(&pdev->dev, "channel %d not present\n", i);
+			free_sja1000dev(netdev);
+			continue;
+		}
+
+		priv->read_reg  = pcan_read_canreg;
+		priv->write_reg = pcan_write_canreg;
+		priv->can.clock.freq = PCC_CAN_CLOCK;
+		priv->ocr = PCC_OCR;
+		priv->cdr = PCC_CDR;
+
+		/* Neither a slave device distributes the clock */
+		if (i > 0)
+			priv->cdr |= CDR_CLK_OFF;
+
+		priv->flags |= SJA1000_CUSTOM_IRQ_HANDLER;
+
+		/* register SJA1000 device */
+		err = register_sja1000dev(netdev);
+		if (err) {
+			free_sja1000dev(netdev);
+			continue;
+		}
+
+		card->channel[i].netdev = netdev;
+		card->chan_count++;
+
+		/* set corresponding led on in the new ccr */
+		ccr &= ~PCC_CCR_LED_OFF_CHAN(i);
+
+		dev_info(&pdev->dev,
+			"%s on channel %d at 0x%p irq %d\n",
+			netdev->name, i, priv->reg_base, pdev->irq);
+	}
+
+	/* write new ccr (change leds state) */
+	pcan_write_reg(card, PCC_CCR, ccr);
+
+	return err;
+}
+
+static int pcan_conf_check(struct pcmcia_device *pdev, void *priv_data)
+{
+	pdev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	pdev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; /* only */
+	pdev->io_lines = 10;
+
+	/* This reserves IO space but doesn't actually enable it */
+	return pcmcia_request_io(pdev);
+}
+
+/*
+ * free all resources used by the device
+ */
+static void pcan_free(struct pcmcia_device *pdev)
+{
+	struct pcan_pccard *card = pdev->priv;
+
+	if (!card)
+		return;
+
+	free_irq(pdev->irq, card);
+	pcan_stop_led_timer(card);
+
+	pcan_free_channels(card);
+
+	ioport_unmap(card->ioport_addr);
+
+	kfree(card);
+	pdev->priv = NULL;
+}
+
+/*
+ * setup PCMCIA socket and probe for PEAK-System PC-CARD
+ */
+static int __devinit pcan_probe(struct pcmcia_device *pdev)
+{
+	struct pcan_pccard *card;
+	int err;
+
+	pdev->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
+
+	err = pcmcia_loop_config(pdev, pcan_conf_check, NULL);
+	if (err) {
+		dev_err(&pdev->dev, "pcmcia_loop_config() error %d\n", err);
+		goto probe_err_1;
+	}
+
+	if (!pdev->irq) {
+		dev_err(&pdev->dev, "no irq assigned\n");
+		err = -ENODEV;
+		goto probe_err_1;
+	}
+
+	err = pcmcia_enable_device(pdev);
+	if (err) {
+		dev_err(&pdev->dev, "pcmcia_enable_device failed err=%d\n",
+			err);
+		goto probe_err_1;
+	}
+
+	card = kzalloc(sizeof(struct pcan_pccard), GFP_KERNEL);
+	if (!card) {
+		dev_err(&pdev->dev, "couldn't allocate card memory\n");
+		err = -ENOMEM;
+		goto probe_err_2;
+	}
+
+	card->pdev = pdev;
+	pdev->priv = card;
+
+	/* sja1000 api uses iomem */
+	card->ioport_addr = ioport_map(pdev->resource[0]->start,
+					resource_size(pdev->resource[0]));
+	if (!card->ioport_addr) {
+		dev_err(&pdev->dev, "couldn't map io port into io memory\n");
+		err = -ENOMEM;
+		goto probe_err_3;
+	}
+	card->fw_major = pcan_read_reg(card, PCC_FW_MAJOR);
+	card->fw_minor = pcan_read_reg(card, PCC_FW_MINOR);
+
+	/* display board name and firware version */
+	dev_info(&pdev->dev, "PEAK-System pcmcia card %s fw %d.%d\n",
+		pdev->prod_id[1] ? pdev->prod_id[1] : "PCAN-PC Card",
+		card->fw_major, card->fw_minor);
+
+	/* detect available channels */
+	pcan_add_channels(card);
+	if (!card->chan_count)
+		goto probe_err_4;
+
+	/* init the timer which controls the leds */
+	init_timer(&card->led_timer);
+	card->led_timer.function = pcan_led_timer;
+	card->led_timer.data = (unsigned long)card;
+
+	/* request the given irq */
+	err = request_irq(pdev->irq, &pcan_isr, IRQF_SHARED, PCC_NAME, card);
+	if (err) {
+		dev_err(&pdev->dev, "couldn't request irq%d\n", pdev->irq);
+		goto probe_err_5;
+	}
+
+	/* power on the connectors */
+	pcan_set_can_power(card, 1);
+
+	return 0;
+
+probe_err_5:
+	/* unregister can devices from network */
+	pcan_free_channels(card);
+
+probe_err_4:
+	ioport_unmap(card->ioport_addr);
+
+probe_err_3:
+	kfree(card);
+	pdev->priv = NULL;
+
+probe_err_2:
+	pcmcia_disable_device(pdev);
+
+probe_err_1:
+	return err;
+}
+
+/*
+ * release claimed resources
+ */
+static void pcan_remove(struct pcmcia_device *pdev)
+{
+	pcan_free(pdev);
+	pcmcia_disable_device(pdev);
+}
+
+static struct pcmcia_driver pcan_driver = {
+	.name = PCC_NAME,
+	.probe = pcan_probe,
+	.remove = pcan_remove,
+	.id_table = pcan_table,
+};
+
+static int __init pcan_init(void)
+{
+	return pcmcia_register_driver(&pcan_driver);
+}
+module_init(pcan_init);
+
+static void __exit pcan_exit(void)
+{
+	pcmcia_unregister_driver(&pcan_driver);
+}
+module_exit(pcan_exit);
-- 
1.7.5.4


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v4.1] can/sja1000: add support for PEAK-System PCMCIA card
  2012-02-16 10:10 [PATCH v4.1] can/sja1000: add support for PEAK-System PCMCIA card Stephane Grosjean
@ 2012-02-17  8:24 ` Dominik Brodowski
  2012-02-17  9:21   ` Stephane Grosjean
  0 siblings, 1 reply; 9+ messages in thread
From: Dominik Brodowski @ 2012-02-17  8:24 UTC (permalink / raw)
  To: Stephane Grosjean
  Cc: Oliver Hartkopp, linux pcmcia Mailing List,
	linux-can Mailing List

On Thu, Feb 16, 2012 at 11:10:42AM +0100, Stephane Grosjean wrote:
> This patch adds the support of the PCAN-PC Card (PCMCIA) card from
> PEAK-System Technik (http://www.peak-system.com). The PCAN-PC Card is
> sja1000 based and exists in 1 or 2 channels.
> 
> Signed-off-by: Stephane Grosjean <s.grosjean@peak-system.com>

for the PCMCIA part: Acked-by: Dominik Brodowski <linux@dominikbrodowski.net>

Best,
	Dominik

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v4.1] can/sja1000: add support for PEAK-System PCMCIA card
  2012-02-17  8:24 ` Dominik Brodowski
@ 2012-02-17  9:21   ` Stephane Grosjean
  2012-02-17  9:36     ` Marc Kleine-Budde
  0 siblings, 1 reply; 9+ messages in thread
From: Stephane Grosjean @ 2012-02-17  9:21 UTC (permalink / raw)
  To: Oliver Hartkopp; +Cc: linux-can@vger.kernel.org

Hi Oliver,

I've got one (newbie) question: what is the next step? I mean, should I 
post the same patch again but with this "Acked-by"? Or what? Should I 
wait for any linux-can Acked-by ?

Best regards,

Stéphane

Le 17/02/2012 09:24, Dominik Brodowski a écrit :
> On Thu, Feb 16, 2012 at 11:10:42AM +0100, Stephane Grosjean wrote:
>> This patch adds the support of the PCAN-PC Card (PCMCIA) card from
>> PEAK-System Technik (http://www.peak-system.com). The PCAN-PC Card is
>> sja1000 based and exists in 1 or 2 channels.
>>
>> Signed-off-by: Stephane Grosjean<s.grosjean@peak-system.com>
> for the PCMCIA part: Acked-by: Dominik Brodowski<linux@dominikbrodowski.net>
>
> Best,
> 	Dominik
> --
> To unsubscribe from this list: send the line "unsubscribe linux-can" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
PEAK-System Technik GmbH, Otto-Roehm-Strasse 69, D-64293 Darmstadt 
Geschaeftsleitung: A.Gach/U.Wilhelm,St.Nr.:007/241/13586 FA Darmstadt 
HRB-9183 Darmstadt, Ust.IdNr.:DE 202220078, WEE-Reg.-Nr.: DE39305391 
Tel.+49 (0)6151-817320 / Fax:+49 (0)6151-817329, info@peak-system.com

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v4.1] can/sja1000: add support for PEAK-System PCMCIA card
  2012-02-17  9:21   ` Stephane Grosjean
@ 2012-02-17  9:36     ` Marc Kleine-Budde
  2012-02-17 13:40       ` Oliver Hartkopp
  0 siblings, 1 reply; 9+ messages in thread
From: Marc Kleine-Budde @ 2012-02-17  9:36 UTC (permalink / raw)
  To: Stephane Grosjean; +Cc: Oliver Hartkopp, linux-can@vger.kernel.org

[-- Attachment #1: Type: text/plain, Size: 670 bytes --]

On 02/17/2012 10:21 AM, Stephane Grosjean wrote:
> Hi Oliver,
> 
> I've got one (newbie) question: what is the next step? I mean, should I
> post the same patch again but with this "Acked-by"? Or what? Should I
> wait for any linux-can Acked-by ?

No need to repost so soon. A Tested-by from Oliver under heavy load
detaching together with the Fix unplug Patch would be fine.

Marc

-- 
Pengutronix e.K.                  | Marc Kleine-Budde           |
Industrial Linux Solutions        | Phone: +49-231-2826-924     |
Vertretung West/Dortmund          | Fax:   +49-5121-206917-5555 |
Amtsgericht Hildesheim, HRA 2686  | http://www.pengutronix.de   |


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 262 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v4.1] can/sja1000: add support for PEAK-System PCMCIA card
  2012-02-17  9:36     ` Marc Kleine-Budde
@ 2012-02-17 13:40       ` Oliver Hartkopp
  2012-02-17 14:19         ` Stephane Grosjean
  0 siblings, 1 reply; 9+ messages in thread
From: Oliver Hartkopp @ 2012-02-17 13:40 UTC (permalink / raw)
  To: Marc Kleine-Budde
  Cc: Stephane Grosjean, Oliver Hartkopp, linux-can@vger.kernel.org

On 17.02.2012 10:36, Marc Kleine-Budde wrote:
> On 02/17/2012 10:21 AM, Stephane Grosjean wrote:
>> Hi Oliver,
>>
>> I've got one (newbie) question: what is the next step? I mean, should I
>> post the same patch again but with this "Acked-by"? Or what? Should I
>> wait for any linux-can Acked-by ?
>
> No need to repost so soon. A Tested-by from Oliver under heavy load
> detaching together with the Fix unplug Patch would be fine.
>

I can confirm that with together with this patch
"[PATCH v2] can: sja1000 fix isr hang when hw is unplugged under load"

http://marc.info/?l=linux-can&m=132932474901518&w=2

there is no freeze on my Dell830 when unplugging the PEAK PCMCIA Card.

Tested-by: Oliver Hartkopp <socketcan@hartkopp.net>

Indeed i got some 'last famous CAN frames' on plug out like this:

can0  10 [8] 37 00 FF FF FF FF FF FF

while it should have been

can0  10 [8] 37 00 00 00 00 00 00 00

So the last message can contain rubbish but it doesn't freeze the machine
anymore. The behavior is now just like we discussed it :-)

I also did not get any freezes with the PEAK PCIeC anymore.

The only remaining issue with PEAK PCIeC is the locking warning about the
PCIeC LED timer lock (&card->led_timer) when i enable and start the interface
under CAN load. I do have a peak_pci v5 driver here - but in v6 only changed
the device_id table right?

To get this problem you need to connect the PCIeC to a CAN bus which is 
already having CAN traffic between at least two functional nodes.

Regards,
Oliver


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v4.1] can/sja1000: add support for PEAK-System PCMCIA card
  2012-02-17 13:40       ` Oliver Hartkopp
@ 2012-02-17 14:19         ` Stephane Grosjean
  2012-02-17 14:40           ` Oliver Hartkopp
  0 siblings, 1 reply; 9+ messages in thread
From: Stephane Grosjean @ 2012-02-17 14:19 UTC (permalink / raw)
  To: Oliver Hartkopp
  Cc: Marc Kleine-Budde, Oliver Hartkopp, linux-can@vger.kernel.org

Le 17/02/2012 14:40, Oliver Hartkopp a écrit :
>
> The only remaining issue with PEAK PCIeC is the locking warning about the
> PCIeC LED timer lock (&card->led_timer) when i enable and start the 
> interface
> under CAN load. I do have a peak_pci v5 driver here - but in v6 only 
> changed
> the device_id table right?

Yes, mainly.
>
> To get this problem you need to connect the PCIeC to a CAN bus which 
> is already having CAN traffic between at least two functional nodes.
>

Does this issue always occurs, each time you configure the interface?.. 
Especially with the "new" sja1000.ko?

Thanks and regards,

Stéphane


--
PEAK-System Technik GmbH, Otto-Roehm-Strasse 69, D-64293 Darmstadt 
Geschaeftsleitung: A.Gach/U.Wilhelm,St.Nr.:007/241/13586 FA Darmstadt 
HRB-9183 Darmstadt, Ust.IdNr.:DE 202220078, WEE-Reg.-Nr.: DE39305391 
Tel.+49 (0)6151-817320 / Fax:+49 (0)6151-817329, info@peak-system.com

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v4.1] can/sja1000: add support for PEAK-System PCMCIA card
  2012-02-17 14:19         ` Stephane Grosjean
@ 2012-02-17 14:40           ` Oliver Hartkopp
  2012-02-17 14:47             ` Stephane Grosjean
  0 siblings, 1 reply; 9+ messages in thread
From: Oliver Hartkopp @ 2012-02-17 14:40 UTC (permalink / raw)
  To: Stephane Grosjean
  Cc: Marc Kleine-Budde, Oliver Hartkopp, linux-can@vger.kernel.org

On 17.02.2012 15:19, Stephane Grosjean wrote:
> Le 17/02/2012 14:40, Oliver Hartkopp a écrit :
>>
>> The only remaining issue with PEAK PCIeC is the locking warning about the
>> PCIeC LED timer lock (&card->led_timer) when i enable and start the interface
>> under CAN load. I do have a peak_pci v5 driver here - but in v6 only changed
>> the device_id table right?
>
> Yes, mainly.
>>
>> To get this problem you need to connect the PCIeC to a CAN bus which is
>> already having CAN traffic between at least two functional nodes.
>>
>
> Does this issue always occurs, each time you configure the interface?..

No. It's exactly the first time i configure the interface from a xterm in 
gnome (after making me su to do so). But only under load.

 > Especially with the "new" sja1000.ko?

That's no difference.

I assume there's some LED locking race at startup (e.g. try to blink fast 
after detecting the traffic).

Regards,
Oliver

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v4.1] can/sja1000: add support for PEAK-System PCMCIA card
  2012-02-17 14:40           ` Oliver Hartkopp
@ 2012-02-17 14:47             ` Stephane Grosjean
  0 siblings, 0 replies; 9+ messages in thread
From: Stephane Grosjean @ 2012-02-17 14:47 UTC (permalink / raw)
  To: Oliver Hartkopp
  Cc: Marc Kleine-Budde, Oliver Hartkopp, linux-can@vger.kernel.org

Le 17/02/2012 15:40, Oliver Hartkopp a écrit :
> On 17.02.2012 15:19, Stephane Grosjean wrote:
>> Le 17/02/2012 14:40, Oliver Hartkopp a écrit :
>>>
>>> The only remaining issue with PEAK PCIeC is the locking warning 
>>> about the
>>> PCIeC LED timer lock (&card->led_timer) when i enable and start the 
>>> interface
>>> under CAN load. I do have a peak_pci v5 driver here - but in v6 only 
>>> changed
>>> the device_id table right?
>>
>> Yes, mainly.
>>>
>>> To get this problem you need to connect the PCIeC to a CAN bus which is
>>> already having CAN traffic between at least two functional nodes.
>>>
>>
>> Does this issue always occurs, each time you configure the interface?..
>
> No. It's exactly the first time i configure the interface from a xterm 
> in gnome (after making me su to do so). But only under load.
>
> > Especially with the "new" sja1000.ko?
>
> That's no difference.
>
> I assume there's some LED locking race at startup (e.g. try to blink 
> fast after detecting the traffic).
>


Well, quite annoying: I'm not able to reproduce the issue at all, with 
my own testbed...
I'll give you feedback asap...

Thanks Oliver,
Regards

Stéphane

--
PEAK-System Technik GmbH, Otto-Roehm-Strasse 69, D-64293 Darmstadt 
Geschaeftsleitung: A.Gach/U.Wilhelm,St.Nr.:007/241/13586 FA Darmstadt 
HRB-9183 Darmstadt, Ust.IdNr.:DE 202220078, WEE-Reg.-Nr.: DE39305391 
Tel.+49 (0)6151-817320 / Fax:+49 (0)6151-817329, info@peak-system.com

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2012-02-17 14:48 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-02-16 10:10 [PATCH v4.1] can/sja1000: add support for PEAK-System PCMCIA card Stephane Grosjean
2012-02-17  8:24 ` Dominik Brodowski
2012-02-17  9:21   ` Stephane Grosjean
2012-02-17  9:36     ` Marc Kleine-Budde
2012-02-17 13:40       ` Oliver Hartkopp
2012-02-17 14:19         ` Stephane Grosjean
2012-02-17 14:40           ` Oliver Hartkopp
2012-02-17 14:47             ` Stephane Grosjean
  -- strict thread matches above, loose matches on Subject: below --
2012-02-13 14:18 Stephane Grosjean

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).