summaryrefslogtreecommitdiff
path: root/drivers/net/can/sja1000/peak_pci.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/can/sja1000/peak_pci.c')
-rw-r--r--drivers/net/can/sja1000/peak_pci.c348
1 files changed, 348 insertions, 0 deletions
diff --git a/drivers/net/can/sja1000/peak_pci.c b/drivers/net/can/sja1000/peak_pci.c
new file mode 100644
index 000000000000..d1089a3d62e0
--- /dev/null
+++ b/drivers/net/can/sja1000/peak_pci.c
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2007 Wolfgang Grandegger <wg@grandegger.com>
+ *
+ * Derived from the PCAN project file driver/src/pcan_pci.c:
+ *
+ * Copyright (C) 2001-2006 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.
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include <linux/io.h>
+
+#include "sja1000.h"
+
+#define DRV_NAME "peak_pci"
+
+MODULE_AUTHOR("Wolfgang Grandegger <wg@grandegger.com>");
+MODULE_DESCRIPTION("Socket-CAN driver for PEAK PCAN PCI cards");
+MODULE_SUPPORTED_DEVICE("PEAK PCAN PCI CAN card");
+MODULE_LICENSE("GPL v2");
+
+struct peak_pci {
+ int channel;
+ struct pci_dev *pci_dev;
+ struct net_device *slave_dev;
+ volatile void __iomem *conf_addr;
+};
+
+#define PEAK_PCI_SINGLE 0 /* single channel device */
+#define PEAK_PCI_MASTER 1 /* multi channel master device */
+#define PEAK_PCI_SLAVE 2 /* multi channel slave device */
+
+#define PEAK_PCI_CAN_CLOCK (16000000 / 2)
+
+#define PEAK_PCI_CDR_SINGLE (CDR_CBP | CDR_CLKOUT_MASK | CDR_CLK_OFF)
+#define PEAK_PCI_CDR_MASTER (CDR_CBP | CDR_CLKOUT_MASK)
+
+#define PEAK_PCI_OCR OCR_TX0_PUSHPULL
+
+/*
+ * Important PITA registers
+ */
+#define PITA_ICR 0x00 /* interrupt control register */
+#define PITA_GPIOICR 0x18 /* general purpose I/O interface
+ control register */
+#define PITA_MISC 0x1C /* miscellanoes register */
+
+#define PCI_CONFIG_PORT_SIZE 0x1000 /* size of the config io-memory */
+#define PCI_PORT_SIZE 0x0400 /* size of a channel io-memory */
+
+#define PEAK_PCI_VENDOR_ID 0x001C /* the PCI device and vendor IDs */
+#define PEAK_PCI_DEVICE_ID 0x0001
+
+static struct pci_device_id peak_pci_tbl[] = {
+ {PEAK_PCI_VENDOR_ID, PEAK_PCI_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID,},
+ {0,}
+};
+
+MODULE_DEVICE_TABLE(pci, peak_pci_tbl);
+
+static u8 peak_pci_read_reg(struct net_device *dev, int port)
+{
+ u8 val;
+ val = readb((const volatile void __iomem *)
+ (dev->base_addr + (port << 2)));
+ return val;
+}
+
+static void peak_pci_write_reg(struct net_device *dev, int port, u8 val)
+{
+ writeb(val, (volatile void __iomem *)
+ (dev->base_addr + (port << 2)));
+}
+
+static void peak_pci_post_irq(struct net_device *dev)
+{
+ struct sja1000_priv *priv = netdev_priv(dev);
+ struct peak_pci *board = priv->priv;
+ u16 icr_low;
+
+ /* Select and clear in Pita stored interrupt */
+ icr_low = readw(board->conf_addr + PITA_ICR);
+ if (board->channel == PEAK_PCI_SLAVE) {
+ if (icr_low & 0x0001)
+ writew(0x0001, board->conf_addr + PITA_ICR);
+ } else {
+ if (icr_low & 0x0002)
+ writew(0x0002, board->conf_addr + PITA_ICR);
+ }
+}
+
+static void peak_pci_del_chan(struct net_device *dev, int init_step)
+{
+ struct sja1000_priv *priv = netdev_priv(dev);
+ struct peak_pci *board;
+ u16 icr_high;
+
+ if (!dev)
+ return;
+ priv = netdev_priv(dev);
+ if (!priv)
+ return;
+ board = priv->priv;
+ if (!board)
+ return;
+
+ switch (init_step) {
+ case 0: /* Full cleanup */
+ printk(KERN_INFO "Removing %s device %s\n",
+ DRV_NAME, dev->name);
+ unregister_sja1000dev(dev);
+ case 4:
+ icr_high = readw(board->conf_addr + PITA_ICR + 2);
+ if (board->channel == PEAK_PCI_SLAVE)
+ icr_high &= ~0x0001;
+ else
+ icr_high &= ~0x0002;
+ writew(icr_high, board->conf_addr + PITA_ICR + 2);
+ case 3:
+ iounmap((void *)dev->base_addr);
+ case 2:
+ if (board->channel != PEAK_PCI_SLAVE)
+ iounmap((void *)board->conf_addr);
+ case 1:
+ free_sja1000dev(dev);
+ break;
+ }
+
+}
+
+static int peak_pci_add_chan(struct pci_dev *pdev, int channel,
+ struct net_device **master_dev)
+{
+ struct net_device *dev;
+ struct sja1000_priv *priv;
+ struct peak_pci *board;
+ u16 icr_high;
+ unsigned long addr;
+ int err, init_step;
+
+ dev = alloc_sja1000dev(sizeof(struct peak_pci));
+ if (dev == NULL)
+ return -ENOMEM;
+ init_step = 1;
+
+ priv = netdev_priv(dev);
+ board = priv->priv;
+
+ board->pci_dev = pdev;
+ board->channel = channel;
+
+ if (channel != PEAK_PCI_SLAVE) {
+
+ addr = pci_resource_start(pdev, 0);
+ board->conf_addr = ioremap(addr, PCI_CONFIG_PORT_SIZE);
+ if (board->conf_addr == 0) {
+ err = -ENODEV;
+ goto failure;
+ }
+ init_step = 2;
+
+ /* Set GPIO control register */
+ writew(0x0005, board->conf_addr + PITA_GPIOICR + 2);
+
+ /* Enable single or dual channel */
+ if (channel == PEAK_PCI_MASTER)
+ writeb(0x00, board->conf_addr + PITA_GPIOICR);
+ else
+ writeb(0x04, board->conf_addr + PITA_GPIOICR);
+ /* Toggle reset */
+ writeb(0x05, board->conf_addr + PITA_MISC + 3);
+ mdelay(5);
+ /* Leave parport mux mode */
+ writeb(0x04, board->conf_addr + PITA_MISC + 3);
+ } else {
+ struct sja1000_priv *master_priv = netdev_priv(*master_dev);
+ struct peak_pci *master_board = master_priv->priv;
+ master_board->slave_dev = dev;
+ board->conf_addr = master_board->conf_addr;
+ }
+
+ addr = pci_resource_start(pdev, 1);
+ if (channel == PEAK_PCI_SLAVE)
+ addr += PCI_PORT_SIZE;
+
+ dev->base_addr = (unsigned long)ioremap(addr, PCI_PORT_SIZE);
+ if (dev->base_addr == 0) {
+ err = -ENOMEM;
+ goto failure;
+ }
+ init_step = 3;
+
+ priv->read_reg = peak_pci_read_reg;
+ priv->write_reg = peak_pci_write_reg;
+ priv->post_irq = peak_pci_post_irq;
+
+ priv->can.bittiming.clock = PEAK_PCI_CAN_CLOCK;
+
+ priv->ocr = PEAK_PCI_OCR;
+
+ if (channel == PEAK_PCI_MASTER)
+ priv->cdr = PEAK_PCI_CDR_MASTER;
+ else
+ priv->cdr = PEAK_PCI_CDR_SINGLE;
+
+ /* Register and setup interrupt handling */
+ dev->irq = pdev->irq;
+ icr_high = readw(board->conf_addr + PITA_ICR + 2);
+ if (channel == PEAK_PCI_SLAVE)
+ icr_high |= 0x0001;
+ else
+ icr_high |= 0x0002;
+ writew(icr_high, board->conf_addr + PITA_ICR + 2);
+ init_step = 4;
+
+ SET_NETDEV_DEV(dev, &pdev->dev);
+
+ /* Register SJA1000 device */
+ err = register_sja1000dev(dev);
+ if (err) {
+ printk(KERN_ERR "Registering %s device failed (err=%d)\n",
+ DRV_NAME, err);
+ goto failure;
+ }
+
+ if (channel != PEAK_PCI_SLAVE)
+ *master_dev = dev;
+
+ printk(KERN_INFO "%s: %s at base_addr=%#lx conf_addr=%p irq=%d\n",
+ DRV_NAME, dev->name, dev->base_addr, board->conf_addr, dev->irq);
+
+ return 0;
+
+failure:
+ peak_pci_del_chan(dev, init_step);
+ return err;
+}
+
+static int __devinit peak_pci_init_one(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ int err;
+ u16 sub_sys_id;
+ struct net_device *master_dev = NULL;
+
+ printk(KERN_INFO "%s: initializing device %04x:%04x\n",
+ DRV_NAME, pdev->vendor, pdev->device);
+
+ err = pci_enable_device(pdev);
+ if (err)
+ goto failure;
+
+ err = pci_request_regions(pdev, DRV_NAME);
+ if (err)
+ goto failure;
+
+ err = pci_read_config_word(pdev, 0x2e, &sub_sys_id);
+ if (err)
+ goto failure_cleanup;
+
+ err = pci_write_config_word(pdev, 0x44, 0);
+ if (err)
+ goto failure_cleanup;
+
+ if (sub_sys_id > 3) {
+ err = peak_pci_add_chan(pdev,
+ PEAK_PCI_MASTER, &master_dev);
+ if (err)
+ goto failure_cleanup;
+
+ err = peak_pci_add_chan(pdev,
+ PEAK_PCI_SLAVE, &master_dev);
+ if (err)
+ goto failure_cleanup;
+ } else {
+ err = peak_pci_add_chan(pdev, PEAK_PCI_SINGLE,
+ &master_dev);
+ if (err)
+ goto failure_cleanup;
+ }
+
+ pci_set_drvdata(pdev, master_dev);
+ return 0;
+
+failure_cleanup:
+ if (master_dev)
+ peak_pci_del_chan(master_dev, 0);
+
+ pci_release_regions(pdev);
+
+failure:
+ return err;
+
+}
+
+static void __devexit peak_pci_remove_one(struct pci_dev *pdev)
+{
+ struct net_device *dev = pci_get_drvdata(pdev);
+ struct sja1000_priv *priv = netdev_priv(dev);
+ struct peak_pci *board = priv->priv;
+
+ if (board->slave_dev)
+ peak_pci_del_chan(board->slave_dev, 0);
+ peak_pci_del_chan(dev, 0);
+
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ pci_set_drvdata(pdev, NULL);
+}
+
+static struct pci_driver peak_pci_driver = {
+ .name = DRV_NAME,
+ .id_table = peak_pci_tbl,
+ .probe = peak_pci_init_one,
+ .remove = __devexit_p(peak_pci_remove_one),
+};
+
+static int __init peak_pci_init(void)
+{
+ return pci_register_driver(&peak_pci_driver);
+}
+
+static void __exit peak_pci_exit(void)
+{
+ pci_unregister_driver(&peak_pci_driver);
+}
+
+module_init(peak_pci_init);
+module_exit(peak_pci_exit);