11ccea77eSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
22d3359f8SAndre B. Oliveira /*
32d3359f8SAndre B. Oliveira * tscan1.c: driver for Technologic Systems TS-CAN1 PC104 boards
42d3359f8SAndre B. Oliveira *
52d3359f8SAndre B. Oliveira * Copyright 2010 Andre B. Oliveira
62d3359f8SAndre B. Oliveira */
72d3359f8SAndre B. Oliveira
8*20c72589SKris Bahnsen /* References:
9*20c72589SKris Bahnsen * - Getting started with TS-CAN1, Technologic Systems, Feb 2022
10*20c72589SKris Bahnsen * https://docs.embeddedts.com/TS-CAN1
112d3359f8SAndre B. Oliveira */
122d3359f8SAndre B. Oliveira
132d3359f8SAndre B. Oliveira #include <linux/init.h>
142d3359f8SAndre B. Oliveira #include <linux/io.h>
152d3359f8SAndre B. Oliveira #include <linux/ioport.h>
162d3359f8SAndre B. Oliveira #include <linux/isa.h>
172d3359f8SAndre B. Oliveira #include <linux/module.h>
182d3359f8SAndre B. Oliveira #include <linux/netdevice.h>
192d3359f8SAndre B. Oliveira #include "sja1000.h"
202d3359f8SAndre B. Oliveira
212d3359f8SAndre B. Oliveira MODULE_DESCRIPTION("Driver for Technologic Systems TS-CAN1 PC104 boards");
222d3359f8SAndre B. Oliveira MODULE_AUTHOR("Andre B. Oliveira <anbadeol@gmail.com>");
232d3359f8SAndre B. Oliveira MODULE_LICENSE("GPL");
242d3359f8SAndre B. Oliveira
252d3359f8SAndre B. Oliveira /* Maximum number of boards (one in each JP1:JP2 setting of IO address) */
262d3359f8SAndre B. Oliveira #define TSCAN1_MAXDEV 4
272d3359f8SAndre B. Oliveira
282d3359f8SAndre B. Oliveira /* PLD registers address offsets */
292d3359f8SAndre B. Oliveira #define TSCAN1_ID1 0
302d3359f8SAndre B. Oliveira #define TSCAN1_ID2 1
312d3359f8SAndre B. Oliveira #define TSCAN1_VERSION 2
322d3359f8SAndre B. Oliveira #define TSCAN1_LED 3
332d3359f8SAndre B. Oliveira #define TSCAN1_PAGE 4
342d3359f8SAndre B. Oliveira #define TSCAN1_MODE 5
352d3359f8SAndre B. Oliveira #define TSCAN1_JUMPERS 6
362d3359f8SAndre B. Oliveira
372d3359f8SAndre B. Oliveira /* PLD board identifier registers magic values */
382d3359f8SAndre B. Oliveira #define TSCAN1_ID1_VALUE 0xf6
392d3359f8SAndre B. Oliveira #define TSCAN1_ID2_VALUE 0xb9
402d3359f8SAndre B. Oliveira
412d3359f8SAndre B. Oliveira /* PLD mode register SJA1000 IO enable bit */
422d3359f8SAndre B. Oliveira #define TSCAN1_MODE_ENABLE 0x40
432d3359f8SAndre B. Oliveira
442d3359f8SAndre B. Oliveira /* PLD jumpers register bits */
452d3359f8SAndre B. Oliveira #define TSCAN1_JP4 0x10
462d3359f8SAndre B. Oliveira #define TSCAN1_JP5 0x20
472d3359f8SAndre B. Oliveira
482d3359f8SAndre B. Oliveira /* PLD IO base addresses start */
492d3359f8SAndre B. Oliveira #define TSCAN1_PLD_ADDRESS 0x150
502d3359f8SAndre B. Oliveira
512d3359f8SAndre B. Oliveira /* PLD register space size */
522d3359f8SAndre B. Oliveira #define TSCAN1_PLD_SIZE 8
532d3359f8SAndre B. Oliveira
542d3359f8SAndre B. Oliveira /* SJA1000 register space size */
552d3359f8SAndre B. Oliveira #define TSCAN1_SJA1000_SIZE 32
562d3359f8SAndre B. Oliveira
572d3359f8SAndre B. Oliveira /* SJA1000 crystal frequency (16MHz) */
582d3359f8SAndre B. Oliveira #define TSCAN1_SJA1000_XTAL 16000000
592d3359f8SAndre B. Oliveira
602d3359f8SAndre B. Oliveira /* SJA1000 IO base addresses */
613c8ac0f2SBill Pemberton static const unsigned short tscan1_sja1000_addresses[] = {
622d3359f8SAndre B. Oliveira 0x100, 0x120, 0x180, 0x1a0, 0x200, 0x240, 0x280, 0x320
632d3359f8SAndre B. Oliveira };
642d3359f8SAndre B. Oliveira
652d3359f8SAndre B. Oliveira /* Read SJA1000 register */
tscan1_read(const struct sja1000_priv * priv,int reg)662d3359f8SAndre B. Oliveira static u8 tscan1_read(const struct sja1000_priv *priv, int reg)
672d3359f8SAndre B. Oliveira {
682d3359f8SAndre B. Oliveira return inb((unsigned long)priv->reg_base + reg);
692d3359f8SAndre B. Oliveira }
702d3359f8SAndre B. Oliveira
712d3359f8SAndre B. Oliveira /* Write SJA1000 register */
tscan1_write(const struct sja1000_priv * priv,int reg,u8 val)722d3359f8SAndre B. Oliveira static void tscan1_write(const struct sja1000_priv *priv, int reg, u8 val)
732d3359f8SAndre B. Oliveira {
742d3359f8SAndre B. Oliveira outb(val, (unsigned long)priv->reg_base + reg);
752d3359f8SAndre B. Oliveira }
762d3359f8SAndre B. Oliveira
772d3359f8SAndre B. Oliveira /* Probe for a TS-CAN1 board with JP2:JP1 jumper setting ID */
tscan1_probe(struct device * dev,unsigned id)783c8ac0f2SBill Pemberton static int tscan1_probe(struct device *dev, unsigned id)
792d3359f8SAndre B. Oliveira {
802d3359f8SAndre B. Oliveira struct net_device *netdev;
812d3359f8SAndre B. Oliveira struct sja1000_priv *priv;
822d3359f8SAndre B. Oliveira unsigned long pld_base, sja1000_base;
832d3359f8SAndre B. Oliveira int irq, i;
842d3359f8SAndre B. Oliveira
852d3359f8SAndre B. Oliveira pld_base = TSCAN1_PLD_ADDRESS + id * TSCAN1_PLD_SIZE;
862d3359f8SAndre B. Oliveira if (!request_region(pld_base, TSCAN1_PLD_SIZE, dev_name(dev)))
872d3359f8SAndre B. Oliveira return -EBUSY;
882d3359f8SAndre B. Oliveira
892d3359f8SAndre B. Oliveira if (inb(pld_base + TSCAN1_ID1) != TSCAN1_ID1_VALUE ||
902d3359f8SAndre B. Oliveira inb(pld_base + TSCAN1_ID2) != TSCAN1_ID2_VALUE) {
912d3359f8SAndre B. Oliveira release_region(pld_base, TSCAN1_PLD_SIZE);
922d3359f8SAndre B. Oliveira return -ENODEV;
932d3359f8SAndre B. Oliveira }
942d3359f8SAndre B. Oliveira
952d3359f8SAndre B. Oliveira switch (inb(pld_base + TSCAN1_JUMPERS) & (TSCAN1_JP4 | TSCAN1_JP5)) {
962d3359f8SAndre B. Oliveira case TSCAN1_JP4:
972d3359f8SAndre B. Oliveira irq = 6;
982d3359f8SAndre B. Oliveira break;
992d3359f8SAndre B. Oliveira case TSCAN1_JP5:
1002d3359f8SAndre B. Oliveira irq = 7;
1012d3359f8SAndre B. Oliveira break;
1022d3359f8SAndre B. Oliveira case TSCAN1_JP4 | TSCAN1_JP5:
1032d3359f8SAndre B. Oliveira irq = 5;
1042d3359f8SAndre B. Oliveira break;
1052d3359f8SAndre B. Oliveira default:
1062d3359f8SAndre B. Oliveira dev_err(dev, "invalid JP4:JP5 setting (no IRQ)\n");
1072d3359f8SAndre B. Oliveira release_region(pld_base, TSCAN1_PLD_SIZE);
1082d3359f8SAndre B. Oliveira return -EINVAL;
1092d3359f8SAndre B. Oliveira }
1102d3359f8SAndre B. Oliveira
1112d3359f8SAndre B. Oliveira netdev = alloc_sja1000dev(0);
1122d3359f8SAndre B. Oliveira if (!netdev) {
1132d3359f8SAndre B. Oliveira release_region(pld_base, TSCAN1_PLD_SIZE);
1142d3359f8SAndre B. Oliveira return -ENOMEM;
1152d3359f8SAndre B. Oliveira }
1162d3359f8SAndre B. Oliveira
1172d3359f8SAndre B. Oliveira dev_set_drvdata(dev, netdev);
1182d3359f8SAndre B. Oliveira SET_NETDEV_DEV(netdev, dev);
1192d3359f8SAndre B. Oliveira
1202d3359f8SAndre B. Oliveira netdev->base_addr = pld_base;
1212d3359f8SAndre B. Oliveira netdev->irq = irq;
1222d3359f8SAndre B. Oliveira
1232d3359f8SAndre B. Oliveira priv = netdev_priv(netdev);
1242d3359f8SAndre B. Oliveira priv->read_reg = tscan1_read;
1252d3359f8SAndre B. Oliveira priv->write_reg = tscan1_write;
1262d3359f8SAndre B. Oliveira priv->can.clock.freq = TSCAN1_SJA1000_XTAL / 2;
1272d3359f8SAndre B. Oliveira priv->cdr = CDR_CBP | CDR_CLK_OFF;
1282d3359f8SAndre B. Oliveira priv->ocr = OCR_TX0_PUSHPULL;
1292d3359f8SAndre B. Oliveira
1302d3359f8SAndre B. Oliveira /* Select the first SJA1000 IO address that is free and that works */
1312d3359f8SAndre B. Oliveira for (i = 0; i < ARRAY_SIZE(tscan1_sja1000_addresses); i++) {
1322d3359f8SAndre B. Oliveira sja1000_base = tscan1_sja1000_addresses[i];
1332d3359f8SAndre B. Oliveira if (!request_region(sja1000_base, TSCAN1_SJA1000_SIZE,
1342d3359f8SAndre B. Oliveira dev_name(dev)))
1352d3359f8SAndre B. Oliveira continue;
1362d3359f8SAndre B. Oliveira
1372d3359f8SAndre B. Oliveira /* Set SJA1000 IO base address and enable it */
1382d3359f8SAndre B. Oliveira outb(TSCAN1_MODE_ENABLE | i, pld_base + TSCAN1_MODE);
1392d3359f8SAndre B. Oliveira
1402d3359f8SAndre B. Oliveira priv->reg_base = (void __iomem *)sja1000_base;
1412d3359f8SAndre B. Oliveira if (!register_sja1000dev(netdev)) {
1422d3359f8SAndre B. Oliveira /* SJA1000 probe succeeded; turn LED off and return */
1432d3359f8SAndre B. Oliveira outb(0, pld_base + TSCAN1_LED);
1442d3359f8SAndre B. Oliveira netdev_info(netdev, "TS-CAN1 at 0x%lx 0x%lx irq %d\n",
1452d3359f8SAndre B. Oliveira pld_base, sja1000_base, irq);
1462d3359f8SAndre B. Oliveira return 0;
1472d3359f8SAndre B. Oliveira }
1482d3359f8SAndre B. Oliveira
1492d3359f8SAndre B. Oliveira /* SJA1000 probe failed; release and try next address */
1502d3359f8SAndre B. Oliveira outb(0, pld_base + TSCAN1_MODE);
1512d3359f8SAndre B. Oliveira release_region(sja1000_base, TSCAN1_SJA1000_SIZE);
1522d3359f8SAndre B. Oliveira }
1532d3359f8SAndre B. Oliveira
1542d3359f8SAndre B. Oliveira dev_err(dev, "failed to assign SJA1000 IO address\n");
1552d3359f8SAndre B. Oliveira dev_set_drvdata(dev, NULL);
1562d3359f8SAndre B. Oliveira free_sja1000dev(netdev);
1572d3359f8SAndre B. Oliveira release_region(pld_base, TSCAN1_PLD_SIZE);
1582d3359f8SAndre B. Oliveira return -ENXIO;
1592d3359f8SAndre B. Oliveira }
1602d3359f8SAndre B. Oliveira
tscan1_remove(struct device * dev,unsigned id)16130e88d01SUwe Kleine-König static void tscan1_remove(struct device *dev, unsigned id /*unused*/)
1622d3359f8SAndre B. Oliveira {
1632d3359f8SAndre B. Oliveira struct net_device *netdev;
1642d3359f8SAndre B. Oliveira struct sja1000_priv *priv;
1652d3359f8SAndre B. Oliveira unsigned long pld_base, sja1000_base;
1662d3359f8SAndre B. Oliveira
1672d3359f8SAndre B. Oliveira netdev = dev_get_drvdata(dev);
1682d3359f8SAndre B. Oliveira unregister_sja1000dev(netdev);
1692d3359f8SAndre B. Oliveira dev_set_drvdata(dev, NULL);
1702d3359f8SAndre B. Oliveira
1712d3359f8SAndre B. Oliveira priv = netdev_priv(netdev);
1722d3359f8SAndre B. Oliveira pld_base = netdev->base_addr;
1732d3359f8SAndre B. Oliveira sja1000_base = (unsigned long)priv->reg_base;
1742d3359f8SAndre B. Oliveira
1752d3359f8SAndre B. Oliveira outb(0, pld_base + TSCAN1_MODE); /* disable SJA1000 IO space */
1762d3359f8SAndre B. Oliveira
1772d3359f8SAndre B. Oliveira release_region(sja1000_base, TSCAN1_SJA1000_SIZE);
1782d3359f8SAndre B. Oliveira release_region(pld_base, TSCAN1_PLD_SIZE);
1792d3359f8SAndre B. Oliveira
1802d3359f8SAndre B. Oliveira free_sja1000dev(netdev);
1812d3359f8SAndre B. Oliveira }
1822d3359f8SAndre B. Oliveira
1832d3359f8SAndre B. Oliveira static struct isa_driver tscan1_isa_driver = {
1842d3359f8SAndre B. Oliveira .probe = tscan1_probe,
1853c8ac0f2SBill Pemberton .remove = tscan1_remove,
1862d3359f8SAndre B. Oliveira .driver = {
1872d3359f8SAndre B. Oliveira .name = "tscan1",
1882d3359f8SAndre B. Oliveira },
1892d3359f8SAndre B. Oliveira };
1902d3359f8SAndre B. Oliveira
191ae5ac9b3SWilliam Breathitt Gray module_isa_driver(tscan1_isa_driver, TSCAN1_MAXDEV);
192