From: Marc Kleine-Budde <mkl@pengutronix.de>
To: Stephane Grosjean <s.grosjean@peak-system.com>
Cc: Oliver Hartkopp <socketcan@hartkopp.net>,
linux-can Mailing List <linux-can@vger.kernel.org>
Subject: Re: Add support for PEAK PCMCIA PCAN-PC card
Date: Tue, 10 Jan 2012 00:23:43 +0100 [thread overview]
Message-ID: <4F0B76FF.3060107@pengutronix.de> (raw)
In-Reply-To: <691329.168246256-sendEmail@ubuntu-i386>
[-- Attachment #1: Type: text/plain, Size: 23175 bytes --]
Hello,
On 01/09/2012 03:51 PM, Stephane Grosjean wrote:
> Please find below a new patch which goal is to include the support of the
> PEAK-System PCMCIA board PCAN-PC Card. This board is SJA1000 based and is
> available as a single or dual CAN channel version.
The driver looks good, I cannot comment on the PCMCIA stuff though., see
comments inline.
Pleae use git send-mail to send your patches, please don't forget the
S-o-b line in the patch description.
> diff --git a/drivers/net/can/sja1000/Kconfig b/drivers/net/can/sja1000/Kconfig
> index 36e9d59..3c1e474 100644
> --- a/drivers/net/can/sja1000/Kconfig
> +++ b/drivers/net/can/sja1000/Kconfig
> @@ -43,6 +43,13 @@ 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 Card"
> + depends on PCMCIA
> + ---help---
> + This driver is for the PCAN PC-CARD PCMCIA card with 1 or 2 channels
> + from PEAK Systems (http://www.peak-system.com).
> +
> config CAN_PEAK_PCI
> tristate "PEAK PCAN PCI/PCIe 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..e409a8b
> --- /dev/null
> +++ b/drivers/net/can/sja1000/peak_pcmcia.c
> @@ -0,0 +1,707 @@
> +/*
> + * CAN driver for PEAK-System PCAN-PCARD
> + * Derived from the PCAN project file driver/src/pcan_pccard.c
> + *
> + * Copyright (C) 2006-2009 PEAK System-Technik GmbH
> + * Copyright (C) 2010-2012 Stephane Grosjean <s.grosjean@peak-system.com>
> + *
> + * 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.
Please remove the address. The FSF sometimes relocates it's office.
> + */
> +#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 <linux/stringify.h>
> +#include "sja1000.h"
> +
> +/* PEAK-System PCMCIA driver name */
> +#define DRV_NAME "peak_pccard"
Can you use the same name for the driver and the .c file?
> +
> +/* PEAK-System PCMCIA driver version */
> +#define DRV_VERSION_MAJOR 0
> +#define DRV_VERSION_MINOR 2
> +#define DRV_VERSION_SUBMINOR 0
> +
> +#define DRV_VERSION_STRING __stringify(DRV_VERSION_MAJOR)"."\
> + __stringify(DRV_VERSION_MINOR)"."\
> + __stringify(DRV_VERSION_SUBMINOR)
please add whitespace before "\"
> +
> +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");
> +MODULE_VERSION(DRV_VERSION_STRING);
> +
> +#define DRV_CHAN_MAX 2
> +
> +#define DRV_CAN_CLOCK (16000000 / 2)
> +
> +#define DRV_MANF_ID 0x0377
> +#define DRV_CARD_ID 0x0001
> +
> +#define DRV_CHAN_SIZE 0x20
> +#define DRV_CHAN_OFF(c) ((c)*DRV_CHAN_SIZE)
please add whitespace before and after "*"
> +#define DRV_COMN_OFF (DRV_CHAN_OFF(DRV_CHAN_MAX))
> +#define DRV_COMN_SIZE 0x40
> +
> +/* common area registers */
> +#define DRV_CCR 0x00
> +#define DRV_CSR 0x02
> +#define DRV_CPR 0x04
> +#define DRV_SPI_DIR 0x06
> +#define DRV_SPI_DOR 0x08
> +#define DRV_SPI_ADR 0x0a
> +#define DRV_SPI_INR 0x0c
> +#define DRV_FW_MAJOR 0x10
> +#define DRV_FW_MINOR 0x12
> +
> +/* CCR bits */
> +#define DRV_CCR_CLK_16 0x00
> +#define DRV_CCR_CLK_10 0x01
> +#define DRV_CCR_CLK_21 0x02
> +#define DRV_CCR_CLK_8 0x03
> +#define DRV_CCR_CLK_MASK DRV_CCR_CLK_8
> +
> +#define DRV_CCR_RST_CHAN(c) (0x01 << ((c) + 2))
> +#define DRV_CCR_RST_ALL (DRV_CCR_RST_CHAN(0) | DRV_CCR_RST_CHAN(1))
> +#define DRV_CCR_RST_MASK DRV_CCR_RST_ALL
> +
> +/* led selection bits */
> +#define DRV_LED(c) (1 << (c))
> +#define DRV_LED_ALL (DRV_LED(0) | DRV_LED(1))
> +
> +/* led state value */
> +#define DRV_LED_ON 0x00
> +#define DRV_LED_FAST 0x01
> +#define DRV_LED_SLOW 0x02
> +#define DRV_LED_OFF 0x03
> +
> +#define DRV_CCR_LED_CHAN(s, c) ((s) << (((c) + 2) << 1))
> +
> +#define DRV_CCR_LED_ON_CHAN(c) DRV_CCR_LED_CHAN(DRV_LED_ON, c)
> +#define DRV_CCR_LED_FAST_CHAN(c) DRV_CCR_LED_CHAN(DRV_LED_FAST, c)
> +#define DRV_CCR_LED_SLOW_CHAN(c) DRV_CCR_LED_CHAN(DRV_LED_SLOW, c)
> +#define DRV_CCR_LED_OFF_CHAN(c) DRV_CCR_LED_CHAN(DRV_LED_OFF, c)
> +#define DRV_CCR_LED_MASK_CHAN(c) DRV_CCR_LED_OFF_CHAN(c)
> +#define DRV_CCR_LED_OFF_ALL (DRV_CCR_LED_OFF_CHAN(0) | \
> + DRV_CCR_LED_OFF_CHAN(1))
> +#define DRV_CCR_LED_MASK DRV_CCR_LED_OFF_ALL
> +
> +#define DRV_CCR_INIT (DRV_CCR_CLK_16 | DRV_CCR_RST_ALL | DRV_CCR_LED_OFF_ALL)
> +
> +#define DRV_CSR_SPI_BUSY 0x04
> +#define DRV_SPI_MAX_WAIT_LOOP 100
> +#define DRV_WRITE_MAX_LOOP 1000
> +
> +/*
> + * 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 DRV_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 DRV_CDR (CDR_CBP | CDR_CLKOUT_MASK)
Can you please properly align all the defines above (or don't align at
all). I personally prefer a common prefix for all functions and defines
used in a driver, maybe use PCAN_ instead of DRV_
> +
> +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 *dev;
> + int chan_count;
> + struct pcan_channel channel[DRV_CHAN_MAX];
> + u8 ccr;
> + void __iomem *ioport_addr;
> + struct timer_list led_timer;
> +};
> +
> +static struct pcmcia_device_id pcan_table[] = {
> + PCMCIA_DEVICE_MANF_CARD(DRV_MANF_ID, DRV_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)
In the functions below port is always u8, here an int.
> +{
> + struct pcan_pccard *card = priv->priv;
> + int c = (priv->reg_base - card->ioport_addr) / DRV_CHAN_SIZE;
> +
> + /* sja1000 register changes control the leds state */
> + switch (port) {
> + case REG_MOD:
I would use:
if (port == REG_MOD), instead of the outer switch.
> + switch (v) {
> + case MOD_RM:
> + /* Reset Mode: set led on */
> + pcan_set_leds(card, DRV_LED(c), DRV_LED_ON);
> + break;
> + case 0x00:
is there a defefine for 0x0, too?
> + /* Normal Mode: led slow blinking and start led timer */
> + pcan_set_leds(card, DRV_LED(c), DRV_LED_SLOW);
> + pcan_start_led_timer(card);
> + break;
> + }
> + break;
> + }
> + iowrite8(v, priv->reg_base + port);
> +}
> +
> +/*
> + * read a register from the common area
> + */
> +static u8 pcan_read_reg(struct pcan_pccard *card, u8 port)
> +{
> + return ioread8(card->ioport_addr + DRV_COMN_OFF + port);
> +}
> +
> +/*
> + * write a register into the common area
> + */
> +static void pcan_write_reg(struct pcan_pccard *card, u8 port, u8 v)
> +{
> + /* cache ccr value */
> + if (port == DRV_CCR) {
> + if (card->ccr == v)
> + return;
> + card->ccr = v;
> + }
> +
> + iowrite8(v, card->ioport_addr + DRV_COMN_OFF + port);
> +}
> +
> +/*
> + * wait for SPI engine while it is busy
> + */
> +static int pcan_wait_spi_busy(struct pcan_pccard *card)
> +{
> + int i;
> +
> + for (i = 0; i < DRV_SPI_MAX_WAIT_LOOP; i++) {
> + if (!(pcan_read_reg(card, DRV_CSR) & DRV_CSR_SPI_BUSY))
> + break;
> + schedule();
How long does it take? Can you use msleep or usleep_range here?
> + }
> +
> + if (i >= DRV_SPI_MAX_WAIT_LOOP) {
> + dev_err(&card->dev->dev, "failure: spi engine busy\n");
please use netdev_err()
> + return -EIO;
> + }
> +
> + 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 */
> + pcan_write_reg(card, DRV_SPI_INR, 0x06);
Can you define some macros for this and the following constants?
> + err = pcan_wait_spi_busy(card);
> + if (err)
> + goto write_eeprom_err;
> +
> + /* wait until write enable */
> + for (i = 0; i < DRV_WRITE_MAX_LOOP; i++) {
> + /* RDSR */
> + pcan_write_reg(card, DRV_SPI_INR, 0x05);
> + err = pcan_wait_spi_busy(card);
> + if (err)
> + goto write_eeprom_err;
> + /* WEL */
> + status = pcan_read_reg(card, DRV_SPI_DIR);
> + if (status & 0x02)
> + break;
> + }
> +
> + if (i >= DRV_WRITE_MAX_LOOP) {
> + err = -EIO;
> + goto write_eeprom_err;
> + }
> +
> + /* set address and data */
> + pcan_write_reg(card, DRV_SPI_ADR, addr & 0xff);
The 3rd argument of pcan_write_reg() is u8, so the 0xff is not needed.
> + pcan_write_reg(card, DRV_SPI_DOR, v);
> +
> + /* write instruction with bit3 set accoridng to address */
> + pcan_write_reg(card, DRV_SPI_INR, ((addr & 0x100) ? 8 : 0) | 0x02);
> + err = pcan_wait_spi_busy(card);
> + if (err)
> + goto write_eeprom_err;
> +
> + /* wait while write in progress */
> + for (i = 0; i < DRV_WRITE_MAX_LOOP; i++) {
> + /* RDSR */
> + pcan_write_reg(card, DRV_SPI_INR, 0x05);
> + err = pcan_wait_spi_busy(card);
> + if (err)
> + goto write_eeprom_err;
> +
> + status = pcan_read_reg(card, DRV_SPI_DIR);
> + if (!(status & 0x01))
> + break;
> + }
> +
> + if (i >= DRV_WRITE_MAX_LOOP) {
> + err = -EIO;
> + goto write_eeprom_err;
> + }
> +
> + return 0;
> +
> +write_eeprom_err:
> + dev_info(&card->dev->dev,
> + "writing 0x%x in eeprom @0x%x failed (err %d)\n",
> + v, addr, err);
netdev_info()
> +
> + return err;
> +}
> +
> +/*
> + * control the leds
> + * @led_mask: should be an or made of DRV_LED(x)
> + * @state: DRV_LED_ON, DRV_LED_OFF, DRV_LED_SLOW or DRV_LED_FAST
> + */
> +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 & DRV_LED(i)) {
> + /* clear corresponding led bits in ccr */
> + ccr &= ~DRV_CCR_LED_MASK_CHAN(i);
> + /* then set new bits */
> + ccr |= DRV_CCR_LED_CHAN(state, i);
> + }
> +
> + /* real write only if something has changed in ccr */
> + pcan_write_reg(card, DRV_CCR, ccr);
> +}
> +
> +/*
> + * enable/disable CAN connector power
> + */
> +static inline void pcan_set_can_power(struct pcan_pccard *card, int onoff)
> +{
> + pcan_write_eeprom(card, 0, (onoff) ? 1 : 0);
> +}
> +
> +/*
> + * 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;
> + u8 ccr = card->ccr;
> + int i, up_count;
> +
> + for (i = up_count = 0; i < card->chan_count; i++) {
Personally I would not initialize up_count in the for() loop, as it is
unrelated to it.
> + /* default is: not configured */
> + ccr &= ~DRV_CCR_LED_MASK_CHAN(i);
> + ccr |= DRV_CCR_LED_ON_CHAN(i);
> +
> + netdev = card->channel[i].netdev;
> + if (!netdev || !(netdev->flags & IFF_UP))
> + continue;
> +
> + up_count++;
> +
> + /* no activity (but configured) */
> + ccr &= ~DRV_CCR_LED_MASK_CHAN(i);
> + ccr |= DRV_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 &= ~DRV_CCR_LED_MASK_CHAN(i);
> + ccr |= DRV_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 &= ~DRV_CCR_LED_MASK_CHAN(i);
> + ccr |= DRV_CCR_LED_FAST_CHAN(i);
> + }
> + }
> +
> + /* write the new leds state */
> + pcan_write_reg(card, DRV_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;
> + struct net_device *netdev;
> + irqreturn_t retval = IRQ_NONE;
> + int i, again;
> +
> + do {
> + again = 0;
> +
> + /* Check interrupt for each channel */
> + for (i = 0; i < card->chan_count; i++) {
> + netdev = card->channel[i].netdev;
> + if (!netdev)
> + continue;
> +
> + if (sja1000_interrupt(irq, netdev) == IRQ_HANDLED)
> + again = 1;
> + }
> +
> + /* At least one channel handled the interrupt */
> + if (again)
> + retval = IRQ_HANDLED;
> +
> + } while (again);
> +
> + return retval;
> +}
> +
> +/*
> + * 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 |= DRV_LED(i);
> +
> + netdev = card->channel[i].netdev;
> + if (!netdev)
> + continue;
> +
> + strncpy(name, netdev->name, IFNAMSIZ);
> + unregister_sja1000dev(netdev);
> + free_sja1000dev(netdev);
> +
> + dev_info(&card->dev->dev, "%s removed\n", name);
netdev_info()...maybe remove altogether
> + }
> +
> + if (pcmcia_dev_present(card->dev)) {
> + pcan_set_leds(card, led_mask, DRV_LED_OFF);
> + pcan_set_can_power(card, 0);
> + }
> +
> + ioport_unmap(card->ioport_addr);
> +}
> +
> +/*
> + * check if a CAN controller is present at the specified location
> + */
> +static inline int pcan_check_channel(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 *dev = card->dev;
> + int i, err;
> + u8 ccr = DRV_CCR_INIT;
> + unsigned long rst_time;
^^^^^^^^
this var is used write only, please remove.
> +
> + /* init common registers (reset channels and leds off) */
> + card->ccr = ~ccr;
> + pcan_write_reg(card, DRV_CCR, ccr);
> + rst_time = jiffies;
> +
> + /* wait 2ms before unresetting channels */
> + mdelay(2);
> +
> + ccr &= ~DRV_CCR_RST_ALL;
> + pcan_write_reg(card, DRV_CCR, ccr);
> +
> + /* create one network device per channel detected */
> + for (i = 0; i < DRV_CHAN_MAX; i++) {
For documentation reasons I suggest to use
ARRAY_SIZE(card->channel) instead of DRV_CHAN_MAX.
> + struct net_device *netdev;
> + struct sja1000_priv *priv;
> +
> + netdev = alloc_sja1000dev(0);
> + if (!netdev) {
> + err = -ENOMEM;
> + break;
please do a proper cleanup if this fails with i != 0.
> + }
> +
> + /* update linkages */
> + priv = netdev_priv(netdev);
> + priv->priv = card;
> + SET_NETDEV_DEV(netdev, &dev->dev);
> +
> + priv->irq_flags = IRQF_SHARED;
> + netdev->irq = dev->irq;
> + priv->reg_base = card->ioport_addr + DRV_CHAN_OFF(i);
> +
> + /* check if channel is present */
> + if (!pcan_check_channel(priv)) {
> + dev_err(&dev->dev, "channel %d not present\n", i);
> + free_sja1000dev(netdev);
same here
> + continue;
> + }
> +
> + priv->read_reg = pcan_read_canreg;
> + priv->write_reg = pcan_write_canreg;
> + priv->can.clock.freq = DRV_CAN_CLOCK;
> + priv->ocr = DRV_OCR;
> + priv->cdr = DRV_CDR;
> + priv->flags |= SJA1000_CUSTOM_IRQ_HANDLER;
> +
> + /* register SJA1000 device */
> + err = register_sja1000dev(netdev);
> + if (err) {
> + free_sja1000dev(netdev);
dito
> + continue;
> + }
> +
> + card->channel[i].netdev = netdev;
> + card->chan_count++;
> +
> + /* set corresponding led on in the new ccr */
> + ccr &= ~DRV_CCR_LED_OFF_CHAN(i);
> +
> + dev_info(&dev->dev,
> + "registered %s on channel %d at 0x%p irq %d\n",
> + netdev->name, i, priv->reg_base, dev->irq);
> + }
> +
> + /* write new ccr (change leds state) */
> + pcan_write_reg(card, DRV_CCR, ccr);
> +
> + return err;
> +}
> +
> +static int pcan_conf_check(struct pcmcia_device *dev, void *priv_data)
> +{
> + dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
> + dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; /* only */
> + dev->io_lines = 10;
> +
> + /* This reserves IO space but doesn't actually enable it */
> + return pcmcia_request_io(dev);
> +}
> +
> +/*
> + * free all resources used by the device
> + */
> +static void pcan_free(struct pcmcia_device *dev)
> +{
> + if (!dev->priv)
> + return;
Can this happen?
> +
> + pcan_stop_led_timer(dev->priv);
> + free_irq(dev->irq, dev->priv);
> + pcan_free_channels(dev->priv);
> +
> + kfree(dev->priv);
> + dev->priv = NULL;
> +}
> +
> +/*
> + * setup PCMCIA socket and probe for PEAK-System PC-CARD
> + */
> +static int __devinit pcan_probe(struct pcmcia_device *dev)
> +{
> + struct pcan_pccard *card;
> + int err;
> +
> + dev->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
> +
> + err = pcmcia_loop_config(dev, pcan_conf_check, NULL);
> + if (err) {
> + dev_err(&dev->dev, "pcmcia_loop_config() error %d\n", err);
> + goto probe_err_1;
> + }
> +
> + dev_info(&dev->dev, "new PEAK-System pcmcia card detected:\n");
> +
> + dev_info(&dev->dev, "%s %s\n",
> + dev->prod_id[0] ? dev->prod_id[0] : "PEAK-System",
> + dev->prod_id[1] ? dev->prod_id[1] : "PCAN-PC Card");
better make it only one dev_info(); there might be other printks
interfering...
> +
> + /*
> + * Note:
> + * base_port = ioport_addr = dev->resource[0]->start
> + * port_count = dev->resource[0]->end
> + */
> +
> + if (!dev->irq) {
> + dev_err(&dev->dev, "no irq assigned\n");
> + err = -ENODEV;
> + goto probe_err_1;
> + }
> +
> + err = pcmcia_enable_device(dev);
> + if (err) {
> + dev_err(&dev->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(&dev->dev, "kzalloc() failure\n");
> + err = -ENOMEM;
> + goto probe_err_2;
> + }
> +
> + card->dev = dev;
> + dev->priv = card;
> +
> + /* sja1000 api uses iomem */
> + card->ioport_addr = ioport_map(dev->resource[0]->start,
> + resource_size(dev->resource[0]));
> + if (!card->ioport_addr) {
> + err = -ENOMEM;
> + goto probe_err_3;
> + }
> +
> + /* display firware version */
> + dev_info(&dev->dev, "firmware %d.%d\n",
> + pcan_read_reg(card, DRV_FW_MAJOR),
> + pcan_read_reg(card, DRV_FW_MINOR));
> +
> + /* detect available channels */
> + pcan_add_channels(card);
> + if (!card->chan_count)
> + goto probe_err_3;
> +
> + /* 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(dev->irq, &pcan_isr, IRQF_SHARED, DRV_NAME, card);
> + if (err)
> + goto probe_err_4;
> +
> + /* power on the connectors */
> + pcan_set_can_power(card, 1);
> +
> + return 0;
> +
> +probe_err_4:
> + /* unregister can devices from network */
> + pcan_free_channels(card);
> +
> +probe_err_3:
> + kfree(card);
> + dev->priv = NULL;
> +
> +probe_err_2:
> + pcmcia_disable_device(dev);
> +
> +probe_err_1:
> + return err;
> +}
> +
> +/*
> + * release claimed resources
> + */
> +static void pcan_remove(struct pcmcia_device *dev)
> +{
> + pcan_free(dev);
> + pcmcia_disable_device(dev);
> +}
> +
> +static struct pcmcia_driver pcan_driver = {
> + .name = DRV_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);
>
cheers, 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 --]
next prev parent reply other threads:[~2012-01-09 23:23 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-01-09 14:51 Add support for PEAK PCMCIA PCAN-PC card Stephane Grosjean
2012-01-09 23:23 ` Marc Kleine-Budde [this message]
2012-01-11 15:13 ` Grosjean Stephane
2012-01-10 13:41 ` Wolfgang Grandegger
2012-01-10 15:05 ` Oliver Hartkopp
2012-01-11 14:11 ` Grosjean Stephane
2012-01-11 14:43 ` Wolfgang Grandegger
2012-01-11 15:00 ` Oliver Hartkopp
2012-01-11 15:11 ` Wolfgang Grandegger
2012-01-11 17:10 ` Grosjean Stephane
2012-01-11 18:35 ` Wolfgang Grandegger
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4F0B76FF.3060107@pengutronix.de \
--to=mkl@pengutronix.de \
--cc=linux-can@vger.kernel.org \
--cc=s.grosjean@peak-system.com \
--cc=socketcan@hartkopp.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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).