18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * tscan1.c: driver for Technologic Systems TS-CAN1 PC104 boards
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright 2010 Andre B. Oliveira
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci/*
98c2ecf20Sopenharmony_ci * References:
108c2ecf20Sopenharmony_ci * - Getting started with TS-CAN1, Technologic Systems, Jun 2009
118c2ecf20Sopenharmony_ci *	http://www.embeddedarm.com/documentation/ts-can1-manual.pdf
128c2ecf20Sopenharmony_ci */
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <linux/init.h>
158c2ecf20Sopenharmony_ci#include <linux/io.h>
168c2ecf20Sopenharmony_ci#include <linux/ioport.h>
178c2ecf20Sopenharmony_ci#include <linux/isa.h>
188c2ecf20Sopenharmony_ci#include <linux/module.h>
198c2ecf20Sopenharmony_ci#include <linux/netdevice.h>
208c2ecf20Sopenharmony_ci#include "sja1000.h"
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Driver for Technologic Systems TS-CAN1 PC104 boards");
238c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andre B. Oliveira <anbadeol@gmail.com>");
248c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci/* Maximum number of boards (one in each JP1:JP2 setting of IO address) */
278c2ecf20Sopenharmony_ci#define TSCAN1_MAXDEV 4
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci/* PLD registers address offsets */
308c2ecf20Sopenharmony_ci#define TSCAN1_ID1	0
318c2ecf20Sopenharmony_ci#define TSCAN1_ID2	1
328c2ecf20Sopenharmony_ci#define TSCAN1_VERSION	2
338c2ecf20Sopenharmony_ci#define TSCAN1_LED	3
348c2ecf20Sopenharmony_ci#define TSCAN1_PAGE	4
358c2ecf20Sopenharmony_ci#define TSCAN1_MODE	5
368c2ecf20Sopenharmony_ci#define TSCAN1_JUMPERS	6
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci/* PLD board identifier registers magic values */
398c2ecf20Sopenharmony_ci#define TSCAN1_ID1_VALUE 0xf6
408c2ecf20Sopenharmony_ci#define TSCAN1_ID2_VALUE 0xb9
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci/* PLD mode register SJA1000 IO enable bit */
438c2ecf20Sopenharmony_ci#define TSCAN1_MODE_ENABLE 0x40
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/* PLD jumpers register bits */
468c2ecf20Sopenharmony_ci#define TSCAN1_JP4 0x10
478c2ecf20Sopenharmony_ci#define TSCAN1_JP5 0x20
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci/* PLD IO base addresses start */
508c2ecf20Sopenharmony_ci#define TSCAN1_PLD_ADDRESS 0x150
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci/* PLD register space size */
538c2ecf20Sopenharmony_ci#define TSCAN1_PLD_SIZE 8
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci/* SJA1000 register space size */
568c2ecf20Sopenharmony_ci#define TSCAN1_SJA1000_SIZE 32
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci/* SJA1000 crystal frequency (16MHz) */
598c2ecf20Sopenharmony_ci#define TSCAN1_SJA1000_XTAL 16000000
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci/* SJA1000 IO base addresses */
628c2ecf20Sopenharmony_cistatic const unsigned short tscan1_sja1000_addresses[] = {
638c2ecf20Sopenharmony_ci	0x100, 0x120, 0x180, 0x1a0, 0x200, 0x240, 0x280, 0x320
648c2ecf20Sopenharmony_ci};
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci/* Read SJA1000 register */
678c2ecf20Sopenharmony_cistatic u8 tscan1_read(const struct sja1000_priv *priv, int reg)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	return inb((unsigned long)priv->reg_base + reg);
708c2ecf20Sopenharmony_ci}
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci/* Write SJA1000 register */
738c2ecf20Sopenharmony_cistatic void tscan1_write(const struct sja1000_priv *priv, int reg, u8 val)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	outb(val, (unsigned long)priv->reg_base + reg);
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci/* Probe for a TS-CAN1 board with JP2:JP1 jumper setting ID */
798c2ecf20Sopenharmony_cistatic int tscan1_probe(struct device *dev, unsigned id)
808c2ecf20Sopenharmony_ci{
818c2ecf20Sopenharmony_ci	struct net_device *netdev;
828c2ecf20Sopenharmony_ci	struct sja1000_priv *priv;
838c2ecf20Sopenharmony_ci	unsigned long pld_base, sja1000_base;
848c2ecf20Sopenharmony_ci	int irq, i;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	pld_base = TSCAN1_PLD_ADDRESS + id * TSCAN1_PLD_SIZE;
878c2ecf20Sopenharmony_ci	if (!request_region(pld_base, TSCAN1_PLD_SIZE, dev_name(dev)))
888c2ecf20Sopenharmony_ci		return -EBUSY;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	if (inb(pld_base + TSCAN1_ID1) != TSCAN1_ID1_VALUE ||
918c2ecf20Sopenharmony_ci	    inb(pld_base + TSCAN1_ID2) != TSCAN1_ID2_VALUE) {
928c2ecf20Sopenharmony_ci		release_region(pld_base, TSCAN1_PLD_SIZE);
938c2ecf20Sopenharmony_ci		return -ENODEV;
948c2ecf20Sopenharmony_ci	}
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	switch (inb(pld_base + TSCAN1_JUMPERS) & (TSCAN1_JP4 | TSCAN1_JP5)) {
978c2ecf20Sopenharmony_ci	case TSCAN1_JP4:
988c2ecf20Sopenharmony_ci		irq = 6;
998c2ecf20Sopenharmony_ci		break;
1008c2ecf20Sopenharmony_ci	case TSCAN1_JP5:
1018c2ecf20Sopenharmony_ci		irq = 7;
1028c2ecf20Sopenharmony_ci		break;
1038c2ecf20Sopenharmony_ci	case TSCAN1_JP4 | TSCAN1_JP5:
1048c2ecf20Sopenharmony_ci		irq = 5;
1058c2ecf20Sopenharmony_ci		break;
1068c2ecf20Sopenharmony_ci	default:
1078c2ecf20Sopenharmony_ci		dev_err(dev, "invalid JP4:JP5 setting (no IRQ)\n");
1088c2ecf20Sopenharmony_ci		release_region(pld_base, TSCAN1_PLD_SIZE);
1098c2ecf20Sopenharmony_ci		return -EINVAL;
1108c2ecf20Sopenharmony_ci	}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	netdev = alloc_sja1000dev(0);
1138c2ecf20Sopenharmony_ci	if (!netdev) {
1148c2ecf20Sopenharmony_ci		release_region(pld_base, TSCAN1_PLD_SIZE);
1158c2ecf20Sopenharmony_ci		return -ENOMEM;
1168c2ecf20Sopenharmony_ci	}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, netdev);
1198c2ecf20Sopenharmony_ci	SET_NETDEV_DEV(netdev, dev);
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	netdev->base_addr = pld_base;
1228c2ecf20Sopenharmony_ci	netdev->irq = irq;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	priv = netdev_priv(netdev);
1258c2ecf20Sopenharmony_ci	priv->read_reg = tscan1_read;
1268c2ecf20Sopenharmony_ci	priv->write_reg = tscan1_write;
1278c2ecf20Sopenharmony_ci	priv->can.clock.freq = TSCAN1_SJA1000_XTAL / 2;
1288c2ecf20Sopenharmony_ci	priv->cdr = CDR_CBP | CDR_CLK_OFF;
1298c2ecf20Sopenharmony_ci	priv->ocr = OCR_TX0_PUSHPULL;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	/* Select the first SJA1000 IO address that is free and that works */
1328c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(tscan1_sja1000_addresses); i++) {
1338c2ecf20Sopenharmony_ci		sja1000_base = tscan1_sja1000_addresses[i];
1348c2ecf20Sopenharmony_ci		if (!request_region(sja1000_base, TSCAN1_SJA1000_SIZE,
1358c2ecf20Sopenharmony_ci								dev_name(dev)))
1368c2ecf20Sopenharmony_ci			continue;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci		/* Set SJA1000 IO base address and enable it */
1398c2ecf20Sopenharmony_ci		outb(TSCAN1_MODE_ENABLE | i, pld_base + TSCAN1_MODE);
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci		priv->reg_base = (void __iomem *)sja1000_base;
1428c2ecf20Sopenharmony_ci		if (!register_sja1000dev(netdev)) {
1438c2ecf20Sopenharmony_ci			/* SJA1000 probe succeeded; turn LED off and return */
1448c2ecf20Sopenharmony_ci			outb(0, pld_base + TSCAN1_LED);
1458c2ecf20Sopenharmony_ci			netdev_info(netdev, "TS-CAN1 at 0x%lx 0x%lx irq %d\n",
1468c2ecf20Sopenharmony_ci						pld_base, sja1000_base, irq);
1478c2ecf20Sopenharmony_ci			return 0;
1488c2ecf20Sopenharmony_ci		}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci		/* SJA1000 probe failed; release and try next address */
1518c2ecf20Sopenharmony_ci		outb(0, pld_base + TSCAN1_MODE);
1528c2ecf20Sopenharmony_ci		release_region(sja1000_base, TSCAN1_SJA1000_SIZE);
1538c2ecf20Sopenharmony_ci	}
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	dev_err(dev, "failed to assign SJA1000 IO address\n");
1568c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, NULL);
1578c2ecf20Sopenharmony_ci	free_sja1000dev(netdev);
1588c2ecf20Sopenharmony_ci	release_region(pld_base, TSCAN1_PLD_SIZE);
1598c2ecf20Sopenharmony_ci	return -ENXIO;
1608c2ecf20Sopenharmony_ci}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_cistatic int tscan1_remove(struct device *dev, unsigned id /*unused*/)
1638c2ecf20Sopenharmony_ci{
1648c2ecf20Sopenharmony_ci	struct net_device *netdev;
1658c2ecf20Sopenharmony_ci	struct sja1000_priv *priv;
1668c2ecf20Sopenharmony_ci	unsigned long pld_base, sja1000_base;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	netdev = dev_get_drvdata(dev);
1698c2ecf20Sopenharmony_ci	unregister_sja1000dev(netdev);
1708c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, NULL);
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	priv = netdev_priv(netdev);
1738c2ecf20Sopenharmony_ci	pld_base = netdev->base_addr;
1748c2ecf20Sopenharmony_ci	sja1000_base = (unsigned long)priv->reg_base;
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	outb(0, pld_base + TSCAN1_MODE);	/* disable SJA1000 IO space */
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	release_region(sja1000_base, TSCAN1_SJA1000_SIZE);
1798c2ecf20Sopenharmony_ci	release_region(pld_base, TSCAN1_PLD_SIZE);
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	free_sja1000dev(netdev);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	return 0;
1848c2ecf20Sopenharmony_ci}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_cistatic struct isa_driver tscan1_isa_driver = {
1878c2ecf20Sopenharmony_ci	.probe = tscan1_probe,
1888c2ecf20Sopenharmony_ci	.remove = tscan1_remove,
1898c2ecf20Sopenharmony_ci	.driver = {
1908c2ecf20Sopenharmony_ci		.name = "tscan1",
1918c2ecf20Sopenharmony_ci	},
1928c2ecf20Sopenharmony_ci};
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_cimodule_isa_driver(tscan1_isa_driver, TSCAN1_MAXDEV);
195