18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * IBM Real-Time Linux driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) IBM Corporation, 2010
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Author: Keith Mannthey <kmannth@us.ibm.com>
88c2ecf20Sopenharmony_ci *         Vernon Mauery <vernux@us.ibm.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <linux/kernel.h>
148c2ecf20Sopenharmony_ci#include <linux/delay.h>
158c2ecf20Sopenharmony_ci#include <linux/module.h>
168c2ecf20Sopenharmony_ci#include <linux/io.h>
178c2ecf20Sopenharmony_ci#include <linux/dmi.h>
188c2ecf20Sopenharmony_ci#include <linux/efi.h>
198c2ecf20Sopenharmony_ci#include <linux/mutex.h>
208c2ecf20Sopenharmony_ci#include <asm/bios_ebda.h>
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#include <linux/io-64-nonatomic-lo-hi.h>
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistatic bool force;
258c2ecf20Sopenharmony_cimodule_param(force, bool, 0);
268c2ecf20Sopenharmony_ciMODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic bool debug;
298c2ecf20Sopenharmony_cimodule_param(debug, bool, 0644);
308c2ecf20Sopenharmony_ciMODULE_PARM_DESC(debug, "Show debug output");
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
338c2ecf20Sopenharmony_ciMODULE_AUTHOR("Keith Mannthey <kmmanth@us.ibm.com>");
348c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vernon Mauery <vernux@us.ibm.com>");
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#define RTL_ADDR_TYPE_IO    1
378c2ecf20Sopenharmony_ci#define RTL_ADDR_TYPE_MMIO  2
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci#define RTL_CMD_ENTER_PRTM  1
408c2ecf20Sopenharmony_ci#define RTL_CMD_EXIT_PRTM   2
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci/* The RTL table as presented by the EBDA: */
438c2ecf20Sopenharmony_cistruct ibm_rtl_table {
448c2ecf20Sopenharmony_ci	char signature[5]; /* signature should be "_RTL_" */
458c2ecf20Sopenharmony_ci	u8 version;
468c2ecf20Sopenharmony_ci	u8 rt_status;
478c2ecf20Sopenharmony_ci	u8 command;
488c2ecf20Sopenharmony_ci	u8 command_status;
498c2ecf20Sopenharmony_ci	u8 cmd_address_type;
508c2ecf20Sopenharmony_ci	u8 cmd_granularity;
518c2ecf20Sopenharmony_ci	u8 cmd_offset;
528c2ecf20Sopenharmony_ci	u16 reserve1;
538c2ecf20Sopenharmony_ci	u32 cmd_port_address; /* platform dependent address */
548c2ecf20Sopenharmony_ci	u32 cmd_port_value;   /* platform dependent value */
558c2ecf20Sopenharmony_ci} __attribute__((packed));
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci/* to locate "_RTL_" signature do a masked 5-byte integer compare */
588c2ecf20Sopenharmony_ci#define RTL_SIGNATURE 0x0000005f4c54525fULL
598c2ecf20Sopenharmony_ci#define RTL_MASK      0x000000ffffffffffULL
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci#define RTL_DEBUG(fmt, ...)				\
628c2ecf20Sopenharmony_cido {							\
638c2ecf20Sopenharmony_ci	if (debug)					\
648c2ecf20Sopenharmony_ci		pr_info(fmt, ##__VA_ARGS__);		\
658c2ecf20Sopenharmony_ci} while (0)
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(rtl_lock);
688c2ecf20Sopenharmony_cistatic struct ibm_rtl_table __iomem *rtl_table;
698c2ecf20Sopenharmony_cistatic void __iomem *ebda_map;
708c2ecf20Sopenharmony_cistatic void __iomem *rtl_cmd_addr;
718c2ecf20Sopenharmony_cistatic u8 rtl_cmd_type;
728c2ecf20Sopenharmony_cistatic u8 rtl_cmd_width;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistatic void __iomem *rtl_port_map(phys_addr_t addr, unsigned long len)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
778c2ecf20Sopenharmony_ci		return ioremap(addr, len);
788c2ecf20Sopenharmony_ci	return ioport_map(addr, len);
798c2ecf20Sopenharmony_ci}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistatic void rtl_port_unmap(void __iomem *addr)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	if (addr && rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
848c2ecf20Sopenharmony_ci		iounmap(addr);
858c2ecf20Sopenharmony_ci	else
868c2ecf20Sopenharmony_ci		ioport_unmap(addr);
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_cistatic int ibm_rtl_write(u8 value)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	int ret = 0, count = 0;
928c2ecf20Sopenharmony_ci	u32 cmd_port_val;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	RTL_DEBUG("%s(%d)\n", __func__, value);
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	value = value == 1 ? RTL_CMD_ENTER_PRTM : RTL_CMD_EXIT_PRTM;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	mutex_lock(&rtl_lock);
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	if (ioread8(&rtl_table->rt_status) != value) {
1018c2ecf20Sopenharmony_ci		iowrite8(value, &rtl_table->command);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci		switch (rtl_cmd_width) {
1048c2ecf20Sopenharmony_ci		case 8:
1058c2ecf20Sopenharmony_ci			cmd_port_val = ioread8(&rtl_table->cmd_port_value);
1068c2ecf20Sopenharmony_ci			RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
1078c2ecf20Sopenharmony_ci			iowrite8((u8)cmd_port_val, rtl_cmd_addr);
1088c2ecf20Sopenharmony_ci			break;
1098c2ecf20Sopenharmony_ci		case 16:
1108c2ecf20Sopenharmony_ci			cmd_port_val = ioread16(&rtl_table->cmd_port_value);
1118c2ecf20Sopenharmony_ci			RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
1128c2ecf20Sopenharmony_ci			iowrite16((u16)cmd_port_val, rtl_cmd_addr);
1138c2ecf20Sopenharmony_ci			break;
1148c2ecf20Sopenharmony_ci		case 32:
1158c2ecf20Sopenharmony_ci			cmd_port_val = ioread32(&rtl_table->cmd_port_value);
1168c2ecf20Sopenharmony_ci			RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
1178c2ecf20Sopenharmony_ci			iowrite32(cmd_port_val, rtl_cmd_addr);
1188c2ecf20Sopenharmony_ci			break;
1198c2ecf20Sopenharmony_ci		}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci		while (ioread8(&rtl_table->command)) {
1228c2ecf20Sopenharmony_ci			msleep(10);
1238c2ecf20Sopenharmony_ci			if (count++ > 500) {
1248c2ecf20Sopenharmony_ci				pr_err("Hardware not responding to "
1258c2ecf20Sopenharmony_ci				       "mode switch request\n");
1268c2ecf20Sopenharmony_ci				ret = -EIO;
1278c2ecf20Sopenharmony_ci				break;
1288c2ecf20Sopenharmony_ci			}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci		}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci		if (ioread8(&rtl_table->command_status)) {
1338c2ecf20Sopenharmony_ci			RTL_DEBUG("command_status reports failed command\n");
1348c2ecf20Sopenharmony_ci			ret = -EIO;
1358c2ecf20Sopenharmony_ci		}
1368c2ecf20Sopenharmony_ci	}
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	mutex_unlock(&rtl_lock);
1398c2ecf20Sopenharmony_ci	return ret;
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_cistatic ssize_t rtl_show_version(struct device *dev,
1438c2ecf20Sopenharmony_ci                                struct device_attribute *attr,
1448c2ecf20Sopenharmony_ci                                char *buf)
1458c2ecf20Sopenharmony_ci{
1468c2ecf20Sopenharmony_ci	return sprintf(buf, "%d\n", (int)ioread8(&rtl_table->version));
1478c2ecf20Sopenharmony_ci}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic ssize_t rtl_show_state(struct device *dev,
1508c2ecf20Sopenharmony_ci                              struct device_attribute *attr,
1518c2ecf20Sopenharmony_ci                              char *buf)
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	return sprintf(buf, "%d\n", ioread8(&rtl_table->rt_status));
1548c2ecf20Sopenharmony_ci}
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_cistatic ssize_t rtl_set_state(struct device *dev,
1578c2ecf20Sopenharmony_ci                             struct device_attribute *attr,
1588c2ecf20Sopenharmony_ci                             const char *buf,
1598c2ecf20Sopenharmony_ci                             size_t count)
1608c2ecf20Sopenharmony_ci{
1618c2ecf20Sopenharmony_ci	ssize_t ret;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	if (count < 1 || count > 2)
1648c2ecf20Sopenharmony_ci		return -EINVAL;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	switch (buf[0]) {
1678c2ecf20Sopenharmony_ci	case '0':
1688c2ecf20Sopenharmony_ci		ret = ibm_rtl_write(0);
1698c2ecf20Sopenharmony_ci		break;
1708c2ecf20Sopenharmony_ci	case '1':
1718c2ecf20Sopenharmony_ci		ret = ibm_rtl_write(1);
1728c2ecf20Sopenharmony_ci		break;
1738c2ecf20Sopenharmony_ci	default:
1748c2ecf20Sopenharmony_ci		ret = -EINVAL;
1758c2ecf20Sopenharmony_ci	}
1768c2ecf20Sopenharmony_ci	if (ret >= 0)
1778c2ecf20Sopenharmony_ci		ret = count;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	return ret;
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cistatic struct bus_type rtl_subsys = {
1838c2ecf20Sopenharmony_ci	.name = "ibm_rtl",
1848c2ecf20Sopenharmony_ci	.dev_name = "ibm_rtl",
1858c2ecf20Sopenharmony_ci};
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_cistatic DEVICE_ATTR(version, S_IRUGO, rtl_show_version, NULL);
1888c2ecf20Sopenharmony_cistatic DEVICE_ATTR(state, 0600, rtl_show_state, rtl_set_state);
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_cistatic struct device_attribute *rtl_attributes[] = {
1918c2ecf20Sopenharmony_ci	&dev_attr_version,
1928c2ecf20Sopenharmony_ci	&dev_attr_state,
1938c2ecf20Sopenharmony_ci	NULL
1948c2ecf20Sopenharmony_ci};
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cistatic int rtl_setup_sysfs(void) {
1988c2ecf20Sopenharmony_ci	int ret, i;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	ret = subsys_system_register(&rtl_subsys, NULL);
2018c2ecf20Sopenharmony_ci	if (!ret) {
2028c2ecf20Sopenharmony_ci		for (i = 0; rtl_attributes[i]; i ++)
2038c2ecf20Sopenharmony_ci			device_create_file(rtl_subsys.dev_root, rtl_attributes[i]);
2048c2ecf20Sopenharmony_ci	}
2058c2ecf20Sopenharmony_ci	return ret;
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_cistatic void rtl_teardown_sysfs(void) {
2098c2ecf20Sopenharmony_ci	int i;
2108c2ecf20Sopenharmony_ci	for (i = 0; rtl_attributes[i]; i ++)
2118c2ecf20Sopenharmony_ci		device_remove_file(rtl_subsys.dev_root, rtl_attributes[i]);
2128c2ecf20Sopenharmony_ci	bus_unregister(&rtl_subsys);
2138c2ecf20Sopenharmony_ci}
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_cistatic const struct dmi_system_id ibm_rtl_dmi_table[] __initconst = {
2178c2ecf20Sopenharmony_ci	{                                                  \
2188c2ecf20Sopenharmony_ci		.matches = {                               \
2198c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "IBM"),  \
2208c2ecf20Sopenharmony_ci		},                                         \
2218c2ecf20Sopenharmony_ci	},
2228c2ecf20Sopenharmony_ci	{ }
2238c2ecf20Sopenharmony_ci};
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_cistatic int __init ibm_rtl_init(void) {
2268c2ecf20Sopenharmony_ci	unsigned long ebda_addr, ebda_size;
2278c2ecf20Sopenharmony_ci	unsigned int ebda_kb;
2288c2ecf20Sopenharmony_ci	int ret = -ENODEV, i;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	if (force)
2318c2ecf20Sopenharmony_ci		pr_warn("module loaded by force\n");
2328c2ecf20Sopenharmony_ci	/* first ensure that we are running on IBM HW */
2338c2ecf20Sopenharmony_ci	else if (efi_enabled(EFI_BOOT) || !dmi_check_system(ibm_rtl_dmi_table))
2348c2ecf20Sopenharmony_ci		return -ENODEV;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	/* Get the address for the Extended BIOS Data Area */
2378c2ecf20Sopenharmony_ci	ebda_addr = get_bios_ebda();
2388c2ecf20Sopenharmony_ci	if (!ebda_addr) {
2398c2ecf20Sopenharmony_ci		RTL_DEBUG("no BIOS EBDA found\n");
2408c2ecf20Sopenharmony_ci		return -ENODEV;
2418c2ecf20Sopenharmony_ci	}
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	ebda_map = ioremap(ebda_addr, 4);
2448c2ecf20Sopenharmony_ci	if (!ebda_map)
2458c2ecf20Sopenharmony_ci		return -ENOMEM;
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci	/* First word in the EDBA is the Size in KB */
2488c2ecf20Sopenharmony_ci	ebda_kb = ioread16(ebda_map);
2498c2ecf20Sopenharmony_ci	RTL_DEBUG("EBDA is %d kB\n", ebda_kb);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	if (ebda_kb == 0)
2528c2ecf20Sopenharmony_ci		goto out;
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	iounmap(ebda_map);
2558c2ecf20Sopenharmony_ci	ebda_size = ebda_kb*1024;
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	/* Remap the whole table */
2588c2ecf20Sopenharmony_ci	ebda_map = ioremap(ebda_addr, ebda_size);
2598c2ecf20Sopenharmony_ci	if (!ebda_map)
2608c2ecf20Sopenharmony_ci		return -ENOMEM;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	/* search for the _RTL_ signature at the start of the table */
2638c2ecf20Sopenharmony_ci	for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) {
2648c2ecf20Sopenharmony_ci		struct ibm_rtl_table __iomem * tmp;
2658c2ecf20Sopenharmony_ci		tmp = (struct ibm_rtl_table __iomem *) (ebda_map+i);
2668c2ecf20Sopenharmony_ci		if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) {
2678c2ecf20Sopenharmony_ci			phys_addr_t addr;
2688c2ecf20Sopenharmony_ci			unsigned int plen;
2698c2ecf20Sopenharmony_ci			RTL_DEBUG("found RTL_SIGNATURE at %p\n", tmp);
2708c2ecf20Sopenharmony_ci			rtl_table = tmp;
2718c2ecf20Sopenharmony_ci			/* The address, value, width and offset are platform
2728c2ecf20Sopenharmony_ci			 * dependent and found in the ibm_rtl_table */
2738c2ecf20Sopenharmony_ci			rtl_cmd_width = ioread8(&rtl_table->cmd_granularity);
2748c2ecf20Sopenharmony_ci			rtl_cmd_type = ioread8(&rtl_table->cmd_address_type);
2758c2ecf20Sopenharmony_ci			RTL_DEBUG("rtl_cmd_width = %u, rtl_cmd_type = %u\n",
2768c2ecf20Sopenharmony_ci				  rtl_cmd_width, rtl_cmd_type);
2778c2ecf20Sopenharmony_ci			addr = ioread32(&rtl_table->cmd_port_address);
2788c2ecf20Sopenharmony_ci			RTL_DEBUG("addr = %#llx\n", (unsigned long long)addr);
2798c2ecf20Sopenharmony_ci			plen = rtl_cmd_width/sizeof(char);
2808c2ecf20Sopenharmony_ci			rtl_cmd_addr = rtl_port_map(addr, plen);
2818c2ecf20Sopenharmony_ci			RTL_DEBUG("rtl_cmd_addr = %p\n", rtl_cmd_addr);
2828c2ecf20Sopenharmony_ci			if (!rtl_cmd_addr) {
2838c2ecf20Sopenharmony_ci				ret = -ENOMEM;
2848c2ecf20Sopenharmony_ci				break;
2858c2ecf20Sopenharmony_ci			}
2868c2ecf20Sopenharmony_ci			ret = rtl_setup_sysfs();
2878c2ecf20Sopenharmony_ci			break;
2888c2ecf20Sopenharmony_ci		}
2898c2ecf20Sopenharmony_ci	}
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ciout:
2928c2ecf20Sopenharmony_ci	if (ret) {
2938c2ecf20Sopenharmony_ci		iounmap(ebda_map);
2948c2ecf20Sopenharmony_ci		rtl_port_unmap(rtl_cmd_addr);
2958c2ecf20Sopenharmony_ci	}
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	return ret;
2988c2ecf20Sopenharmony_ci}
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_cistatic void __exit ibm_rtl_exit(void)
3018c2ecf20Sopenharmony_ci{
3028c2ecf20Sopenharmony_ci	if (rtl_table) {
3038c2ecf20Sopenharmony_ci		RTL_DEBUG("cleaning up");
3048c2ecf20Sopenharmony_ci		/* do not leave the machine in SMI-free mode */
3058c2ecf20Sopenharmony_ci		ibm_rtl_write(0);
3068c2ecf20Sopenharmony_ci		/* unmap, unlink and remove all traces */
3078c2ecf20Sopenharmony_ci		rtl_teardown_sysfs();
3088c2ecf20Sopenharmony_ci		iounmap(ebda_map);
3098c2ecf20Sopenharmony_ci		rtl_port_unmap(rtl_cmd_addr);
3108c2ecf20Sopenharmony_ci	}
3118c2ecf20Sopenharmony_ci}
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_cimodule_init(ibm_rtl_init);
3148c2ecf20Sopenharmony_cimodule_exit(ibm_rtl_exit);
315