xref: /openbmc/linux/drivers/platform/x86/ibm_rtl.c (revision c72b844ed2f55c442b464e382a2eb2ecab5292a8)
135f0ce03SVernon Mauery /*
235f0ce03SVernon Mauery  * IBM Real-Time Linux driver
335f0ce03SVernon Mauery  *
435f0ce03SVernon Mauery  * This program is free software; you can redistribute it and/or modify
535f0ce03SVernon Mauery  * it under the terms of the GNU General Public License as published by
635f0ce03SVernon Mauery  * the Free Software Foundation; either version 2 of the License, or
735f0ce03SVernon Mauery  * (at your option) any later version.
835f0ce03SVernon Mauery  *
935f0ce03SVernon Mauery  * This program is distributed in the hope that it will be useful,
1035f0ce03SVernon Mauery  * but WITHOUT ANY WARRANTY; without even the implied warranty of
1135f0ce03SVernon Mauery  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1235f0ce03SVernon Mauery  * GNU General Public License for more details.
1335f0ce03SVernon Mauery  *
1435f0ce03SVernon Mauery  * You should have received a copy of the GNU General Public License
1535f0ce03SVernon Mauery  * along with this program; if not, write to the Free Software
1635f0ce03SVernon Mauery  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
1735f0ce03SVernon Mauery  *
1835f0ce03SVernon Mauery  * Copyright (C) IBM Corporation, 2010
1935f0ce03SVernon Mauery  *
2035f0ce03SVernon Mauery  * Author: Keith Mannthey <kmannth@us.ibm.com>
2135f0ce03SVernon Mauery  *         Vernon Mauery <vernux@us.ibm.com>
2235f0ce03SVernon Mauery  *
2335f0ce03SVernon Mauery  */
2435f0ce03SVernon Mauery 
2535f0ce03SVernon Mauery #include <linux/kernel.h>
2635f0ce03SVernon Mauery #include <linux/delay.h>
2735f0ce03SVernon Mauery #include <linux/module.h>
2835f0ce03SVernon Mauery #include <linux/io.h>
2935f0ce03SVernon Mauery #include <linux/sysdev.h>
3035f0ce03SVernon Mauery #include <linux/dmi.h>
3135f0ce03SVernon Mauery #include <linux/mutex.h>
3235f0ce03SVernon Mauery #include <asm/bios_ebda.h>
3335f0ce03SVernon Mauery 
3435f0ce03SVernon Mauery static bool force;
3535f0ce03SVernon Mauery module_param(force, bool, 0);
3635f0ce03SVernon Mauery MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
3735f0ce03SVernon Mauery 
3835f0ce03SVernon Mauery static bool debug;
3935f0ce03SVernon Mauery module_param(debug, bool, 0644);
4035f0ce03SVernon Mauery MODULE_PARM_DESC(debug, "Show debug output");
4135f0ce03SVernon Mauery 
4235f0ce03SVernon Mauery MODULE_LICENSE("GPL");
4335f0ce03SVernon Mauery MODULE_AUTHOR("Keith Mannthey <kmmanth@us.ibm.com>");
4435f0ce03SVernon Mauery MODULE_AUTHOR("Vernon Mauery <vernux@us.ibm.com>");
4535f0ce03SVernon Mauery 
4635f0ce03SVernon Mauery #define RTL_ADDR_TYPE_IO    1
4735f0ce03SVernon Mauery #define RTL_ADDR_TYPE_MMIO  2
4835f0ce03SVernon Mauery 
4935f0ce03SVernon Mauery #define RTL_CMD_ENTER_PRTM  1
5035f0ce03SVernon Mauery #define RTL_CMD_EXIT_PRTM   2
5135f0ce03SVernon Mauery 
5235f0ce03SVernon Mauery /* The RTL table as presented by the EBDA: */
5335f0ce03SVernon Mauery struct ibm_rtl_table {
5435f0ce03SVernon Mauery 	char signature[5]; /* signature should be "_RTL_" */
5535f0ce03SVernon Mauery 	u8 version;
5635f0ce03SVernon Mauery 	u8 rt_status;
5735f0ce03SVernon Mauery 	u8 command;
5835f0ce03SVernon Mauery 	u8 command_status;
5935f0ce03SVernon Mauery 	u8 cmd_address_type;
6035f0ce03SVernon Mauery 	u8 cmd_granularity;
6135f0ce03SVernon Mauery 	u8 cmd_offset;
6235f0ce03SVernon Mauery 	u16 reserve1;
6335f0ce03SVernon Mauery 	u32 cmd_port_address; /* platform dependent address */
6435f0ce03SVernon Mauery 	u32 cmd_port_value;   /* platform dependent value */
6535f0ce03SVernon Mauery } __attribute__((packed));
6635f0ce03SVernon Mauery 
6735f0ce03SVernon Mauery /* to locate "_RTL_" signature do a masked 5-byte integer compare */
6835f0ce03SVernon Mauery #define RTL_SIGNATURE 0x0000005f4c54525fULL
6935f0ce03SVernon Mauery #define RTL_MASK      0x000000ffffffffffULL
7035f0ce03SVernon Mauery 
7135f0ce03SVernon Mauery #define RTL_DEBUG(A, ...) do { \
7235f0ce03SVernon Mauery 	if (debug) \
7335f0ce03SVernon Mauery 		pr_info("ibm-rtl: " A, ##__VA_ARGS__ ); \
7435f0ce03SVernon Mauery } while (0)
7535f0ce03SVernon Mauery 
7635f0ce03SVernon Mauery static DEFINE_MUTEX(rtl_lock);
7735f0ce03SVernon Mauery static struct ibm_rtl_table __iomem *rtl_table;
7835f0ce03SVernon Mauery static void __iomem *ebda_map;
7935f0ce03SVernon Mauery static void __iomem *rtl_cmd_addr;
8035f0ce03SVernon Mauery static u8 rtl_cmd_type;
8135f0ce03SVernon Mauery static u8 rtl_cmd_width;
8235f0ce03SVernon Mauery 
8335f0ce03SVernon Mauery static void __iomem *rtl_port_map(phys_addr_t addr, unsigned long len)
8435f0ce03SVernon Mauery {
8535f0ce03SVernon Mauery 	if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
8635f0ce03SVernon Mauery 		return ioremap(addr, len);
8735f0ce03SVernon Mauery 	return ioport_map(addr, len);
8835f0ce03SVernon Mauery }
8935f0ce03SVernon Mauery 
9035f0ce03SVernon Mauery static void rtl_port_unmap(void __iomem *addr)
9135f0ce03SVernon Mauery {
9235f0ce03SVernon Mauery 	if (addr && rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
9335f0ce03SVernon Mauery 		iounmap(addr);
9435f0ce03SVernon Mauery 	else
9535f0ce03SVernon Mauery 		ioport_unmap(addr);
9635f0ce03SVernon Mauery }
9735f0ce03SVernon Mauery 
9835f0ce03SVernon Mauery static int ibm_rtl_write(u8 value)
9935f0ce03SVernon Mauery {
10035f0ce03SVernon Mauery 	int ret = 0, count = 0;
10135f0ce03SVernon Mauery 	static u32 cmd_port_val;
10235f0ce03SVernon Mauery 
10335f0ce03SVernon Mauery 	RTL_DEBUG("%s(%d)\n", __FUNCTION__, value);
10435f0ce03SVernon Mauery 
10535f0ce03SVernon Mauery 	value = value == 1 ? RTL_CMD_ENTER_PRTM : RTL_CMD_EXIT_PRTM;
10635f0ce03SVernon Mauery 
10735f0ce03SVernon Mauery 	mutex_lock(&rtl_lock);
10835f0ce03SVernon Mauery 
10935f0ce03SVernon Mauery 	if (ioread8(&rtl_table->rt_status) != value) {
11035f0ce03SVernon Mauery 		iowrite8(value, &rtl_table->command);
11135f0ce03SVernon Mauery 
11235f0ce03SVernon Mauery 		switch (rtl_cmd_width) {
11335f0ce03SVernon Mauery 		case 8:
11435f0ce03SVernon Mauery 			cmd_port_val = ioread8(&rtl_table->cmd_port_value);
11535f0ce03SVernon Mauery 			RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
11635f0ce03SVernon Mauery 			iowrite8((u8)cmd_port_val, rtl_cmd_addr);
11735f0ce03SVernon Mauery 			break;
11835f0ce03SVernon Mauery 		case 16:
11935f0ce03SVernon Mauery 			cmd_port_val = ioread16(&rtl_table->cmd_port_value);
12035f0ce03SVernon Mauery 			RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
12135f0ce03SVernon Mauery 			iowrite16((u16)cmd_port_val, rtl_cmd_addr);
12235f0ce03SVernon Mauery 			break;
12335f0ce03SVernon Mauery 		case 32:
12435f0ce03SVernon Mauery 			cmd_port_val = ioread32(&rtl_table->cmd_port_value);
12535f0ce03SVernon Mauery 			RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
12635f0ce03SVernon Mauery 			iowrite32(cmd_port_val, rtl_cmd_addr);
12735f0ce03SVernon Mauery 			break;
12835f0ce03SVernon Mauery 		}
12935f0ce03SVernon Mauery 
13035f0ce03SVernon Mauery 		while (ioread8(&rtl_table->command)) {
13135f0ce03SVernon Mauery 			msleep(10);
13235f0ce03SVernon Mauery 			if (count++ > 500) {
13335f0ce03SVernon Mauery 				pr_err("ibm-rtl: Hardware not responding to "
13435f0ce03SVernon Mauery 					"mode switch request\n");
13535f0ce03SVernon Mauery 				ret = -EIO;
13635f0ce03SVernon Mauery 				break;
13735f0ce03SVernon Mauery 			}
13835f0ce03SVernon Mauery 
13935f0ce03SVernon Mauery 		}
14035f0ce03SVernon Mauery 
14135f0ce03SVernon Mauery 		if (ioread8(&rtl_table->command_status)) {
14235f0ce03SVernon Mauery 			RTL_DEBUG("command_status reports failed command\n");
14335f0ce03SVernon Mauery 			ret = -EIO;
14435f0ce03SVernon Mauery 		}
14535f0ce03SVernon Mauery 	}
14635f0ce03SVernon Mauery 
14735f0ce03SVernon Mauery 	mutex_unlock(&rtl_lock);
14835f0ce03SVernon Mauery 	return ret;
14935f0ce03SVernon Mauery }
15035f0ce03SVernon Mauery 
15135f0ce03SVernon Mauery static ssize_t rtl_show_version(struct sysdev_class * dev,
15235f0ce03SVernon Mauery                                 struct sysdev_class_attribute *attr,
15335f0ce03SVernon Mauery                                 char *buf)
15435f0ce03SVernon Mauery {
15535f0ce03SVernon Mauery 	return sprintf(buf, "%d\n", (int)ioread8(&rtl_table->version));
15635f0ce03SVernon Mauery }
15735f0ce03SVernon Mauery 
15835f0ce03SVernon Mauery static ssize_t rtl_show_state(struct sysdev_class *dev,
15935f0ce03SVernon Mauery                               struct sysdev_class_attribute *attr,
16035f0ce03SVernon Mauery                               char *buf)
16135f0ce03SVernon Mauery {
16235f0ce03SVernon Mauery 	return sprintf(buf, "%d\n", ioread8(&rtl_table->rt_status));
16335f0ce03SVernon Mauery }
16435f0ce03SVernon Mauery 
16535f0ce03SVernon Mauery static ssize_t rtl_set_state(struct sysdev_class *dev,
16635f0ce03SVernon Mauery                              struct sysdev_class_attribute *attr,
16735f0ce03SVernon Mauery                              const char *buf,
16835f0ce03SVernon Mauery                              size_t count)
16935f0ce03SVernon Mauery {
17035f0ce03SVernon Mauery 	ssize_t ret;
17135f0ce03SVernon Mauery 
17235f0ce03SVernon Mauery 	if (count < 1 || count > 2)
17335f0ce03SVernon Mauery 		return -EINVAL;
17435f0ce03SVernon Mauery 
17535f0ce03SVernon Mauery 	switch (buf[0]) {
17635f0ce03SVernon Mauery 	case '0':
17735f0ce03SVernon Mauery 		ret = ibm_rtl_write(0);
17835f0ce03SVernon Mauery 		break;
17935f0ce03SVernon Mauery 	case '1':
18035f0ce03SVernon Mauery 		ret = ibm_rtl_write(1);
18135f0ce03SVernon Mauery 		break;
18235f0ce03SVernon Mauery 	default:
18335f0ce03SVernon Mauery 		ret = -EINVAL;
18435f0ce03SVernon Mauery 	}
18535f0ce03SVernon Mauery 	if (ret >= 0)
18635f0ce03SVernon Mauery 		ret = count;
18735f0ce03SVernon Mauery 
18835f0ce03SVernon Mauery 	return ret;
18935f0ce03SVernon Mauery }
19035f0ce03SVernon Mauery 
19135f0ce03SVernon Mauery static struct sysdev_class class_rtl = {
19235f0ce03SVernon Mauery 	.name = "ibm_rtl",
19335f0ce03SVernon Mauery };
19435f0ce03SVernon Mauery 
19535f0ce03SVernon Mauery static SYSDEV_CLASS_ATTR(version, S_IRUGO, rtl_show_version, NULL);
19635f0ce03SVernon Mauery static SYSDEV_CLASS_ATTR(state, 0600, rtl_show_state, rtl_set_state);
19735f0ce03SVernon Mauery 
19835f0ce03SVernon Mauery static struct sysdev_class_attribute *rtl_attributes[] = {
19935f0ce03SVernon Mauery 	&attr_version,
20035f0ce03SVernon Mauery 	&attr_state,
20135f0ce03SVernon Mauery 	NULL
20235f0ce03SVernon Mauery };
20335f0ce03SVernon Mauery 
20435f0ce03SVernon Mauery 
20535f0ce03SVernon Mauery static int rtl_setup_sysfs(void) {
20635f0ce03SVernon Mauery 	int ret, i;
20735f0ce03SVernon Mauery 	ret = sysdev_class_register(&class_rtl);
20835f0ce03SVernon Mauery 
20935f0ce03SVernon Mauery 	if (!ret) {
21035f0ce03SVernon Mauery 		for (i = 0; rtl_attributes[i]; i ++)
21135f0ce03SVernon Mauery 			sysdev_class_create_file(&class_rtl, rtl_attributes[i]);
21235f0ce03SVernon Mauery 	}
21335f0ce03SVernon Mauery 	return ret;
21435f0ce03SVernon Mauery }
21535f0ce03SVernon Mauery 
21635f0ce03SVernon Mauery static void rtl_teardown_sysfs(void) {
21735f0ce03SVernon Mauery 	int i;
21835f0ce03SVernon Mauery 	for (i = 0; rtl_attributes[i]; i ++)
21935f0ce03SVernon Mauery 		sysdev_class_remove_file(&class_rtl, rtl_attributes[i]);
22035f0ce03SVernon Mauery 	sysdev_class_unregister(&class_rtl);
22135f0ce03SVernon Mauery }
22235f0ce03SVernon Mauery 
22335f0ce03SVernon Mauery static int dmi_check_cb(const struct dmi_system_id *id)
22435f0ce03SVernon Mauery {
22535f0ce03SVernon Mauery 	RTL_DEBUG("found IBM server '%s'\n", id->ident);
22635f0ce03SVernon Mauery 	return 0;
22735f0ce03SVernon Mauery }
22835f0ce03SVernon Mauery 
22935f0ce03SVernon Mauery #define ibm_dmi_entry(NAME, TYPE)                  \
23035f0ce03SVernon Mauery {                                                  \
23135f0ce03SVernon Mauery 	.ident = NAME,                             \
23235f0ce03SVernon Mauery 	.matches = {                               \
23335f0ce03SVernon Mauery 		DMI_MATCH(DMI_SYS_VENDOR, "IBM"),  \
23435f0ce03SVernon Mauery 		DMI_MATCH(DMI_PRODUCT_NAME, TYPE), \
23535f0ce03SVernon Mauery 	},                                         \
23635f0ce03SVernon Mauery 	.callback = dmi_check_cb                   \
23735f0ce03SVernon Mauery }
23835f0ce03SVernon Mauery 
23935f0ce03SVernon Mauery static struct dmi_system_id __initdata ibm_rtl_dmi_table[] = {
24035f0ce03SVernon Mauery 	ibm_dmi_entry("BladeCenter LS21", "7971"),
24135f0ce03SVernon Mauery 	ibm_dmi_entry("BladeCenter LS22", "7901"),
24235f0ce03SVernon Mauery 	ibm_dmi_entry("BladeCenter HS21 XM", "7995"),
24335f0ce03SVernon Mauery 	ibm_dmi_entry("BladeCenter HS22", "7870"),
24435f0ce03SVernon Mauery 	ibm_dmi_entry("BladeCenter HS22V", "7871"),
24535f0ce03SVernon Mauery 	ibm_dmi_entry("System x3550 M2", "7946"),
24635f0ce03SVernon Mauery 	ibm_dmi_entry("System x3650 M2", "7947"),
24735f0ce03SVernon Mauery 	ibm_dmi_entry("System x3550 M3", "7944"),
24835f0ce03SVernon Mauery 	ibm_dmi_entry("System x3650 M3", "7945"),
24935f0ce03SVernon Mauery 	{ }
25035f0ce03SVernon Mauery };
25135f0ce03SVernon Mauery 
25235f0ce03SVernon Mauery static int __init ibm_rtl_init(void) {
25335f0ce03SVernon Mauery 	unsigned long ebda_addr, ebda_size;
25435f0ce03SVernon Mauery 	unsigned int ebda_kb;
25535f0ce03SVernon Mauery 	int ret = -ENODEV, i;
25635f0ce03SVernon Mauery 
25735f0ce03SVernon Mauery 	if (force)
25835f0ce03SVernon Mauery 		pr_warning("ibm-rtl: module loaded by force\n");
25935f0ce03SVernon Mauery 	/* first ensure that we are running on IBM HW */
26035f0ce03SVernon Mauery 	else if (!dmi_check_system(ibm_rtl_dmi_table))
26135f0ce03SVernon Mauery 		return -ENODEV;
26235f0ce03SVernon Mauery 
26335f0ce03SVernon Mauery 	/* Get the address for the Extended BIOS Data Area */
26435f0ce03SVernon Mauery 	ebda_addr = get_bios_ebda();
26535f0ce03SVernon Mauery 	if (!ebda_addr) {
26635f0ce03SVernon Mauery 		RTL_DEBUG("no BIOS EBDA found\n");
26735f0ce03SVernon Mauery 		return -ENODEV;
26835f0ce03SVernon Mauery 	}
26935f0ce03SVernon Mauery 
27035f0ce03SVernon Mauery 	ebda_map = ioremap(ebda_addr, 4);
27135f0ce03SVernon Mauery 	if (!ebda_map)
27235f0ce03SVernon Mauery 		return -ENOMEM;
27335f0ce03SVernon Mauery 
27435f0ce03SVernon Mauery 	/* First word in the EDBA is the Size in KB */
27535f0ce03SVernon Mauery 	ebda_kb = ioread16(ebda_map);
27635f0ce03SVernon Mauery 	RTL_DEBUG("EBDA is %d kB\n", ebda_kb);
27735f0ce03SVernon Mauery 
27835f0ce03SVernon Mauery 	if (ebda_kb == 0)
27935f0ce03SVernon Mauery 		goto out;
28035f0ce03SVernon Mauery 
28135f0ce03SVernon Mauery 	iounmap(ebda_map);
28235f0ce03SVernon Mauery 	ebda_size = ebda_kb*1024;
28335f0ce03SVernon Mauery 
28435f0ce03SVernon Mauery 	/* Remap the whole table */
28535f0ce03SVernon Mauery 	ebda_map = ioremap(ebda_addr, ebda_size);
28635f0ce03SVernon Mauery 	if (!ebda_map)
28735f0ce03SVernon Mauery 		return -ENOMEM;
28835f0ce03SVernon Mauery 
28935f0ce03SVernon Mauery 	/* search for the _RTL_ signature at the start of the table */
29035f0ce03SVernon Mauery 	for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) {
29135f0ce03SVernon Mauery 		struct ibm_rtl_table __iomem * tmp;
29235f0ce03SVernon Mauery 		tmp = (struct ibm_rtl_table __iomem *) (ebda_map+i);
29335f0ce03SVernon Mauery 		if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) {
29435f0ce03SVernon Mauery 			phys_addr_t addr;
29535f0ce03SVernon Mauery 			unsigned int plen;
29635f0ce03SVernon Mauery 			RTL_DEBUG("found RTL_SIGNATURE at %#llx\n", (u64)tmp);
29735f0ce03SVernon Mauery 			rtl_table = tmp;
29835f0ce03SVernon Mauery 			/* The address, value, width and offset are platform
29935f0ce03SVernon Mauery 			 * dependent and found in the ibm_rtl_table */
30035f0ce03SVernon Mauery 			rtl_cmd_width = ioread8(&rtl_table->cmd_granularity);
30135f0ce03SVernon Mauery 			rtl_cmd_type = ioread8(&rtl_table->cmd_address_type);
30235f0ce03SVernon Mauery 			RTL_DEBUG("rtl_cmd_width = %u, rtl_cmd_type = %u\n",
30335f0ce03SVernon Mauery 			      rtl_cmd_width, rtl_cmd_type);
30435f0ce03SVernon Mauery 			addr = ioread32(&rtl_table->cmd_port_address);
305*c72b844eSRandy Dunlap 			RTL_DEBUG("addr = %#llx\n", (unsigned long long)addr);
30635f0ce03SVernon Mauery 			plen = rtl_cmd_width/sizeof(char);
30735f0ce03SVernon Mauery 			rtl_cmd_addr = rtl_port_map(addr, plen);
30835f0ce03SVernon Mauery 			RTL_DEBUG("rtl_cmd_addr = %#llx\n", (u64)rtl_cmd_addr);
30935f0ce03SVernon Mauery 			if (!rtl_cmd_addr) {
31035f0ce03SVernon Mauery 				ret = -ENOMEM;
31135f0ce03SVernon Mauery 				break;
31235f0ce03SVernon Mauery 			}
31335f0ce03SVernon Mauery 			ret = rtl_setup_sysfs();
31435f0ce03SVernon Mauery 			break;
31535f0ce03SVernon Mauery 		}
31635f0ce03SVernon Mauery 	}
31735f0ce03SVernon Mauery 
31835f0ce03SVernon Mauery out:
31935f0ce03SVernon Mauery 	if (ret) {
32035f0ce03SVernon Mauery 		iounmap(ebda_map);
32135f0ce03SVernon Mauery 		rtl_port_unmap(rtl_cmd_addr);
32235f0ce03SVernon Mauery 	}
32335f0ce03SVernon Mauery 
32435f0ce03SVernon Mauery 	return ret;
32535f0ce03SVernon Mauery }
32635f0ce03SVernon Mauery 
32735f0ce03SVernon Mauery static void __exit ibm_rtl_exit(void)
32835f0ce03SVernon Mauery {
32935f0ce03SVernon Mauery 	if (rtl_table) {
33035f0ce03SVernon Mauery 		RTL_DEBUG("cleaning up");
33135f0ce03SVernon Mauery 		/* do not leave the machine in SMI-free mode */
33235f0ce03SVernon Mauery 		ibm_rtl_write(0);
33335f0ce03SVernon Mauery 		/* unmap, unlink and remove all traces */
33435f0ce03SVernon Mauery 		rtl_teardown_sysfs();
33535f0ce03SVernon Mauery 		iounmap(ebda_map);
33635f0ce03SVernon Mauery 		rtl_port_unmap(rtl_cmd_addr);
33735f0ce03SVernon Mauery 	}
33835f0ce03SVernon Mauery }
33935f0ce03SVernon Mauery 
34035f0ce03SVernon Mauery module_init(ibm_rtl_init);
34135f0ce03SVernon Mauery module_exit(ibm_rtl_exit);
342