18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * cpcihp_generic.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Generic port I/O CompactPCI driver
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Copyright 2002 SOMA Networks, Inc.
88c2ecf20Sopenharmony_ci * Copyright 2001 Intel San Luis Obispo
98c2ecf20Sopenharmony_ci * Copyright 2000,2001 MontaVista Software Inc.
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * This generic CompactPCI hotplug driver should allow using the PCI hotplug
128c2ecf20Sopenharmony_ci * mechanism on any CompactPCI board that exposes the #ENUM signal as a bit
138c2ecf20Sopenharmony_ci * in a system register that can be read through standard port I/O.
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci * Send feedback to <scottm@somanetworks.com>
168c2ecf20Sopenharmony_ci */
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#include <linux/module.h>
198c2ecf20Sopenharmony_ci#include <linux/init.h>
208c2ecf20Sopenharmony_ci#include <linux/errno.h>
218c2ecf20Sopenharmony_ci#include <linux/pci.h>
228c2ecf20Sopenharmony_ci#include <linux/string.h>
238c2ecf20Sopenharmony_ci#include "cpci_hotplug.h"
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define DRIVER_VERSION	"0.1"
268c2ecf20Sopenharmony_ci#define DRIVER_AUTHOR	"Scott Murray <scottm@somanetworks.com>"
278c2ecf20Sopenharmony_ci#define DRIVER_DESC	"Generic port I/O CompactPCI Hot Plug Driver"
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#if !defined(MODULE)
308c2ecf20Sopenharmony_ci#define MY_NAME	"cpcihp_generic"
318c2ecf20Sopenharmony_ci#else
328c2ecf20Sopenharmony_ci#define MY_NAME	THIS_MODULE->name
338c2ecf20Sopenharmony_ci#endif
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci#define dbg(format, arg...)					\
368c2ecf20Sopenharmony_ci	do {							\
378c2ecf20Sopenharmony_ci		if (debug)					\
388c2ecf20Sopenharmony_ci			printk(KERN_DEBUG "%s: " format "\n",	\
398c2ecf20Sopenharmony_ci				MY_NAME, ## arg);		\
408c2ecf20Sopenharmony_ci	} while (0)
418c2ecf20Sopenharmony_ci#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME, ## arg)
428c2ecf20Sopenharmony_ci#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME, ## arg)
438c2ecf20Sopenharmony_ci#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME, ## arg)
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/* local variables */
468c2ecf20Sopenharmony_cistatic bool debug;
478c2ecf20Sopenharmony_cistatic char *bridge;
488c2ecf20Sopenharmony_cistatic u8 bridge_busnr;
498c2ecf20Sopenharmony_cistatic u8 bridge_slot;
508c2ecf20Sopenharmony_cistatic struct pci_bus *bus;
518c2ecf20Sopenharmony_cistatic u8 first_slot;
528c2ecf20Sopenharmony_cistatic u8 last_slot;
538c2ecf20Sopenharmony_cistatic u16 port;
548c2ecf20Sopenharmony_cistatic unsigned int enum_bit;
558c2ecf20Sopenharmony_cistatic u8 enum_mask;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_cistatic struct cpci_hp_controller_ops generic_hpc_ops;
588c2ecf20Sopenharmony_cistatic struct cpci_hp_controller generic_hpc;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic int __init validate_parameters(void)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	char *str;
638c2ecf20Sopenharmony_ci	char *p;
648c2ecf20Sopenharmony_ci	unsigned long tmp;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	if (!bridge) {
678c2ecf20Sopenharmony_ci		info("not configured, disabling.");
688c2ecf20Sopenharmony_ci		return -EINVAL;
698c2ecf20Sopenharmony_ci	}
708c2ecf20Sopenharmony_ci	str = bridge;
718c2ecf20Sopenharmony_ci	if (!*str)
728c2ecf20Sopenharmony_ci		return -EINVAL;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	tmp = simple_strtoul(str, &p, 16);
758c2ecf20Sopenharmony_ci	if (p == str || tmp > 0xff) {
768c2ecf20Sopenharmony_ci		err("Invalid hotplug bus bridge device bus number");
778c2ecf20Sopenharmony_ci		return -EINVAL;
788c2ecf20Sopenharmony_ci	}
798c2ecf20Sopenharmony_ci	bridge_busnr = (u8) tmp;
808c2ecf20Sopenharmony_ci	dbg("bridge_busnr = 0x%02x", bridge_busnr);
818c2ecf20Sopenharmony_ci	if (*p != ':') {
828c2ecf20Sopenharmony_ci		err("Invalid hotplug bus bridge device");
838c2ecf20Sopenharmony_ci		return -EINVAL;
848c2ecf20Sopenharmony_ci	}
858c2ecf20Sopenharmony_ci	str = p + 1;
868c2ecf20Sopenharmony_ci	tmp = simple_strtoul(str, &p, 16);
878c2ecf20Sopenharmony_ci	if (p == str || tmp > 0x1f) {
888c2ecf20Sopenharmony_ci		err("Invalid hotplug bus bridge device slot number");
898c2ecf20Sopenharmony_ci		return -EINVAL;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci	bridge_slot = (u8) tmp;
928c2ecf20Sopenharmony_ci	dbg("bridge_slot = 0x%02x", bridge_slot);
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	dbg("first_slot = 0x%02x", first_slot);
958c2ecf20Sopenharmony_ci	dbg("last_slot = 0x%02x", last_slot);
968c2ecf20Sopenharmony_ci	if (!(first_slot && last_slot)) {
978c2ecf20Sopenharmony_ci		err("Need to specify first_slot and last_slot");
988c2ecf20Sopenharmony_ci		return -EINVAL;
998c2ecf20Sopenharmony_ci	}
1008c2ecf20Sopenharmony_ci	if (last_slot < first_slot) {
1018c2ecf20Sopenharmony_ci		err("first_slot must be less than last_slot");
1028c2ecf20Sopenharmony_ci		return -EINVAL;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	dbg("port = 0x%04x", port);
1068c2ecf20Sopenharmony_ci	dbg("enum_bit = 0x%02x", enum_bit);
1078c2ecf20Sopenharmony_ci	if (enum_bit > 7) {
1088c2ecf20Sopenharmony_ci		err("Invalid #ENUM bit");
1098c2ecf20Sopenharmony_ci		return -EINVAL;
1108c2ecf20Sopenharmony_ci	}
1118c2ecf20Sopenharmony_ci	enum_mask = 1 << enum_bit;
1128c2ecf20Sopenharmony_ci	return 0;
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistatic int query_enum(void)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	u8 value;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	value = inb_p(port);
1208c2ecf20Sopenharmony_ci	return ((value & enum_mask) == enum_mask);
1218c2ecf20Sopenharmony_ci}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_cistatic int __init cpcihp_generic_init(void)
1248c2ecf20Sopenharmony_ci{
1258c2ecf20Sopenharmony_ci	int status;
1268c2ecf20Sopenharmony_ci	struct resource *r;
1278c2ecf20Sopenharmony_ci	struct pci_dev *dev;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	info(DRIVER_DESC " version: " DRIVER_VERSION);
1308c2ecf20Sopenharmony_ci	status = validate_parameters();
1318c2ecf20Sopenharmony_ci	if (status)
1328c2ecf20Sopenharmony_ci		return status;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	r = request_region(port, 1, "#ENUM hotswap signal register");
1358c2ecf20Sopenharmony_ci	if (!r)
1368c2ecf20Sopenharmony_ci		return -EBUSY;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	dev = pci_get_domain_bus_and_slot(0, bridge_busnr,
1398c2ecf20Sopenharmony_ci					  PCI_DEVFN(bridge_slot, 0));
1408c2ecf20Sopenharmony_ci	if (!dev || dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) {
1418c2ecf20Sopenharmony_ci		err("Invalid bridge device %s", bridge);
1428c2ecf20Sopenharmony_ci		pci_dev_put(dev);
1438c2ecf20Sopenharmony_ci		return -EINVAL;
1448c2ecf20Sopenharmony_ci	}
1458c2ecf20Sopenharmony_ci	bus = dev->subordinate;
1468c2ecf20Sopenharmony_ci	pci_dev_put(dev);
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	memset(&generic_hpc, 0, sizeof(struct cpci_hp_controller));
1498c2ecf20Sopenharmony_ci	generic_hpc_ops.query_enum = query_enum;
1508c2ecf20Sopenharmony_ci	generic_hpc.ops = &generic_hpc_ops;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	status = cpci_hp_register_controller(&generic_hpc);
1538c2ecf20Sopenharmony_ci	if (status != 0) {
1548c2ecf20Sopenharmony_ci		err("Could not register cPCI hotplug controller");
1558c2ecf20Sopenharmony_ci		return -ENODEV;
1568c2ecf20Sopenharmony_ci	}
1578c2ecf20Sopenharmony_ci	dbg("registered controller");
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	status = cpci_hp_register_bus(bus, first_slot, last_slot);
1608c2ecf20Sopenharmony_ci	if (status != 0) {
1618c2ecf20Sopenharmony_ci		err("Could not register cPCI hotplug bus");
1628c2ecf20Sopenharmony_ci		goto init_bus_register_error;
1638c2ecf20Sopenharmony_ci	}
1648c2ecf20Sopenharmony_ci	dbg("registered bus");
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	status = cpci_hp_start();
1678c2ecf20Sopenharmony_ci	if (status != 0) {
1688c2ecf20Sopenharmony_ci		err("Could not started cPCI hotplug system");
1698c2ecf20Sopenharmony_ci		goto init_start_error;
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci	dbg("started cpci hp system");
1728c2ecf20Sopenharmony_ci	return 0;
1738c2ecf20Sopenharmony_ciinit_start_error:
1748c2ecf20Sopenharmony_ci	cpci_hp_unregister_bus(bus);
1758c2ecf20Sopenharmony_ciinit_bus_register_error:
1768c2ecf20Sopenharmony_ci	cpci_hp_unregister_controller(&generic_hpc);
1778c2ecf20Sopenharmony_ci	err("status = %d", status);
1788c2ecf20Sopenharmony_ci	return status;
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cistatic void __exit cpcihp_generic_exit(void)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	cpci_hp_stop();
1858c2ecf20Sopenharmony_ci	cpci_hp_unregister_bus(bus);
1868c2ecf20Sopenharmony_ci	cpci_hp_unregister_controller(&generic_hpc);
1878c2ecf20Sopenharmony_ci	release_region(port, 1);
1888c2ecf20Sopenharmony_ci}
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_cimodule_init(cpcihp_generic_init);
1918c2ecf20Sopenharmony_cimodule_exit(cpcihp_generic_exit);
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR);
1948c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC);
1958c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
1968c2ecf20Sopenharmony_cimodule_param(debug, bool, S_IRUGO | S_IWUSR);
1978c2ecf20Sopenharmony_ciMODULE_PARM_DESC(debug, "Debugging mode enabled or not");
1988c2ecf20Sopenharmony_cimodule_param(bridge, charp, 0);
1998c2ecf20Sopenharmony_ciMODULE_PARM_DESC(bridge, "Hotswap bus bridge device, <bus>:<slot> (bus and slot are in hexadecimal)");
2008c2ecf20Sopenharmony_cimodule_param(first_slot, byte, 0);
2018c2ecf20Sopenharmony_ciMODULE_PARM_DESC(first_slot, "Hotswap bus first slot number");
2028c2ecf20Sopenharmony_cimodule_param(last_slot, byte, 0);
2038c2ecf20Sopenharmony_ciMODULE_PARM_DESC(last_slot, "Hotswap bus last slot number");
2048c2ecf20Sopenharmony_cimodule_param_hw(port, ushort, ioport, 0);
2058c2ecf20Sopenharmony_ciMODULE_PARM_DESC(port, "#ENUM signal I/O port");
2068c2ecf20Sopenharmony_cimodule_param(enum_bit, uint, 0);
2078c2ecf20Sopenharmony_ciMODULE_PARM_DESC(enum_bit, "#ENUM signal bit (0-7)");
208