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 25*323623a7SJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 26*323623a7SJoe Perches 2735f0ce03SVernon Mauery #include <linux/kernel.h> 2835f0ce03SVernon Mauery #include <linux/delay.h> 2935f0ce03SVernon Mauery #include <linux/module.h> 3035f0ce03SVernon Mauery #include <linux/io.h> 3135f0ce03SVernon Mauery #include <linux/sysdev.h> 3235f0ce03SVernon Mauery #include <linux/dmi.h> 331d37db77SVernon Mauery #include <linux/efi.h> 3435f0ce03SVernon Mauery #include <linux/mutex.h> 3535f0ce03SVernon Mauery #include <asm/bios_ebda.h> 3635f0ce03SVernon Mauery 3735f0ce03SVernon Mauery static bool force; 3835f0ce03SVernon Mauery module_param(force, bool, 0); 3935f0ce03SVernon Mauery MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); 4035f0ce03SVernon Mauery 4135f0ce03SVernon Mauery static bool debug; 4235f0ce03SVernon Mauery module_param(debug, bool, 0644); 4335f0ce03SVernon Mauery MODULE_PARM_DESC(debug, "Show debug output"); 4435f0ce03SVernon Mauery 4535f0ce03SVernon Mauery MODULE_LICENSE("GPL"); 4635f0ce03SVernon Mauery MODULE_AUTHOR("Keith Mannthey <kmmanth@us.ibm.com>"); 4735f0ce03SVernon Mauery MODULE_AUTHOR("Vernon Mauery <vernux@us.ibm.com>"); 4835f0ce03SVernon Mauery 4935f0ce03SVernon Mauery #define RTL_ADDR_TYPE_IO 1 5035f0ce03SVernon Mauery #define RTL_ADDR_TYPE_MMIO 2 5135f0ce03SVernon Mauery 5235f0ce03SVernon Mauery #define RTL_CMD_ENTER_PRTM 1 5335f0ce03SVernon Mauery #define RTL_CMD_EXIT_PRTM 2 5435f0ce03SVernon Mauery 5535f0ce03SVernon Mauery /* The RTL table as presented by the EBDA: */ 5635f0ce03SVernon Mauery struct ibm_rtl_table { 5735f0ce03SVernon Mauery char signature[5]; /* signature should be "_RTL_" */ 5835f0ce03SVernon Mauery u8 version; 5935f0ce03SVernon Mauery u8 rt_status; 6035f0ce03SVernon Mauery u8 command; 6135f0ce03SVernon Mauery u8 command_status; 6235f0ce03SVernon Mauery u8 cmd_address_type; 6335f0ce03SVernon Mauery u8 cmd_granularity; 6435f0ce03SVernon Mauery u8 cmd_offset; 6535f0ce03SVernon Mauery u16 reserve1; 6635f0ce03SVernon Mauery u32 cmd_port_address; /* platform dependent address */ 6735f0ce03SVernon Mauery u32 cmd_port_value; /* platform dependent value */ 6835f0ce03SVernon Mauery } __attribute__((packed)); 6935f0ce03SVernon Mauery 7035f0ce03SVernon Mauery /* to locate "_RTL_" signature do a masked 5-byte integer compare */ 7135f0ce03SVernon Mauery #define RTL_SIGNATURE 0x0000005f4c54525fULL 7235f0ce03SVernon Mauery #define RTL_MASK 0x000000ffffffffffULL 7335f0ce03SVernon Mauery 74*323623a7SJoe Perches #define RTL_DEBUG(fmt, ...) \ 75*323623a7SJoe Perches do { \ 7635f0ce03SVernon Mauery if (debug) \ 77*323623a7SJoe Perches pr_info(fmt, ##__VA_ARGS__); \ 7835f0ce03SVernon Mauery } while (0) 7935f0ce03SVernon Mauery 8035f0ce03SVernon Mauery static DEFINE_MUTEX(rtl_lock); 8135f0ce03SVernon Mauery static struct ibm_rtl_table __iomem *rtl_table; 8235f0ce03SVernon Mauery static void __iomem *ebda_map; 8335f0ce03SVernon Mauery static void __iomem *rtl_cmd_addr; 8435f0ce03SVernon Mauery static u8 rtl_cmd_type; 8535f0ce03SVernon Mauery static u8 rtl_cmd_width; 8635f0ce03SVernon Mauery 87dbee8a0aSRoland Dreier #ifndef readq 88dbee8a0aSRoland Dreier static inline __u64 readq(const volatile void __iomem *addr) 89dbee8a0aSRoland Dreier { 90dbee8a0aSRoland Dreier const volatile u32 __iomem *p = addr; 91dbee8a0aSRoland Dreier u32 low, high; 92dbee8a0aSRoland Dreier 93dbee8a0aSRoland Dreier low = readl(p); 94dbee8a0aSRoland Dreier high = readl(p + 1); 95dbee8a0aSRoland Dreier 96dbee8a0aSRoland Dreier return low + ((u64)high << 32); 97dbee8a0aSRoland Dreier } 98dbee8a0aSRoland Dreier #endif 99dbee8a0aSRoland Dreier 10035f0ce03SVernon Mauery static void __iomem *rtl_port_map(phys_addr_t addr, unsigned long len) 10135f0ce03SVernon Mauery { 10235f0ce03SVernon Mauery if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO) 10335f0ce03SVernon Mauery return ioremap(addr, len); 10435f0ce03SVernon Mauery return ioport_map(addr, len); 10535f0ce03SVernon Mauery } 10635f0ce03SVernon Mauery 10735f0ce03SVernon Mauery static void rtl_port_unmap(void __iomem *addr) 10835f0ce03SVernon Mauery { 10935f0ce03SVernon Mauery if (addr && rtl_cmd_type == RTL_ADDR_TYPE_MMIO) 11035f0ce03SVernon Mauery iounmap(addr); 11135f0ce03SVernon Mauery else 11235f0ce03SVernon Mauery ioport_unmap(addr); 11335f0ce03SVernon Mauery } 11435f0ce03SVernon Mauery 11535f0ce03SVernon Mauery static int ibm_rtl_write(u8 value) 11635f0ce03SVernon Mauery { 11735f0ce03SVernon Mauery int ret = 0, count = 0; 11835f0ce03SVernon Mauery static u32 cmd_port_val; 11935f0ce03SVernon Mauery 120*323623a7SJoe Perches RTL_DEBUG("%s(%d)\n", __func__, value); 12135f0ce03SVernon Mauery 12235f0ce03SVernon Mauery value = value == 1 ? RTL_CMD_ENTER_PRTM : RTL_CMD_EXIT_PRTM; 12335f0ce03SVernon Mauery 12435f0ce03SVernon Mauery mutex_lock(&rtl_lock); 12535f0ce03SVernon Mauery 12635f0ce03SVernon Mauery if (ioread8(&rtl_table->rt_status) != value) { 12735f0ce03SVernon Mauery iowrite8(value, &rtl_table->command); 12835f0ce03SVernon Mauery 12935f0ce03SVernon Mauery switch (rtl_cmd_width) { 13035f0ce03SVernon Mauery case 8: 13135f0ce03SVernon Mauery cmd_port_val = ioread8(&rtl_table->cmd_port_value); 13235f0ce03SVernon Mauery RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val); 13335f0ce03SVernon Mauery iowrite8((u8)cmd_port_val, rtl_cmd_addr); 13435f0ce03SVernon Mauery break; 13535f0ce03SVernon Mauery case 16: 13635f0ce03SVernon Mauery cmd_port_val = ioread16(&rtl_table->cmd_port_value); 13735f0ce03SVernon Mauery RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val); 13835f0ce03SVernon Mauery iowrite16((u16)cmd_port_val, rtl_cmd_addr); 13935f0ce03SVernon Mauery break; 14035f0ce03SVernon Mauery case 32: 14135f0ce03SVernon Mauery cmd_port_val = ioread32(&rtl_table->cmd_port_value); 14235f0ce03SVernon Mauery RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val); 14335f0ce03SVernon Mauery iowrite32(cmd_port_val, rtl_cmd_addr); 14435f0ce03SVernon Mauery break; 14535f0ce03SVernon Mauery } 14635f0ce03SVernon Mauery 14735f0ce03SVernon Mauery while (ioread8(&rtl_table->command)) { 14835f0ce03SVernon Mauery msleep(10); 14935f0ce03SVernon Mauery if (count++ > 500) { 150*323623a7SJoe Perches pr_err("Hardware not responding to " 15135f0ce03SVernon Mauery "mode switch request\n"); 15235f0ce03SVernon Mauery ret = -EIO; 15335f0ce03SVernon Mauery break; 15435f0ce03SVernon Mauery } 15535f0ce03SVernon Mauery 15635f0ce03SVernon Mauery } 15735f0ce03SVernon Mauery 15835f0ce03SVernon Mauery if (ioread8(&rtl_table->command_status)) { 15935f0ce03SVernon Mauery RTL_DEBUG("command_status reports failed command\n"); 16035f0ce03SVernon Mauery ret = -EIO; 16135f0ce03SVernon Mauery } 16235f0ce03SVernon Mauery } 16335f0ce03SVernon Mauery 16435f0ce03SVernon Mauery mutex_unlock(&rtl_lock); 16535f0ce03SVernon Mauery return ret; 16635f0ce03SVernon Mauery } 16735f0ce03SVernon Mauery 16835f0ce03SVernon Mauery static ssize_t rtl_show_version(struct sysdev_class * dev, 16935f0ce03SVernon Mauery struct sysdev_class_attribute *attr, 17035f0ce03SVernon Mauery char *buf) 17135f0ce03SVernon Mauery { 17235f0ce03SVernon Mauery return sprintf(buf, "%d\n", (int)ioread8(&rtl_table->version)); 17335f0ce03SVernon Mauery } 17435f0ce03SVernon Mauery 17535f0ce03SVernon Mauery static ssize_t rtl_show_state(struct sysdev_class *dev, 17635f0ce03SVernon Mauery struct sysdev_class_attribute *attr, 17735f0ce03SVernon Mauery char *buf) 17835f0ce03SVernon Mauery { 17935f0ce03SVernon Mauery return sprintf(buf, "%d\n", ioread8(&rtl_table->rt_status)); 18035f0ce03SVernon Mauery } 18135f0ce03SVernon Mauery 18235f0ce03SVernon Mauery static ssize_t rtl_set_state(struct sysdev_class *dev, 18335f0ce03SVernon Mauery struct sysdev_class_attribute *attr, 18435f0ce03SVernon Mauery const char *buf, 18535f0ce03SVernon Mauery size_t count) 18635f0ce03SVernon Mauery { 18735f0ce03SVernon Mauery ssize_t ret; 18835f0ce03SVernon Mauery 18935f0ce03SVernon Mauery if (count < 1 || count > 2) 19035f0ce03SVernon Mauery return -EINVAL; 19135f0ce03SVernon Mauery 19235f0ce03SVernon Mauery switch (buf[0]) { 19335f0ce03SVernon Mauery case '0': 19435f0ce03SVernon Mauery ret = ibm_rtl_write(0); 19535f0ce03SVernon Mauery break; 19635f0ce03SVernon Mauery case '1': 19735f0ce03SVernon Mauery ret = ibm_rtl_write(1); 19835f0ce03SVernon Mauery break; 19935f0ce03SVernon Mauery default: 20035f0ce03SVernon Mauery ret = -EINVAL; 20135f0ce03SVernon Mauery } 20235f0ce03SVernon Mauery if (ret >= 0) 20335f0ce03SVernon Mauery ret = count; 20435f0ce03SVernon Mauery 20535f0ce03SVernon Mauery return ret; 20635f0ce03SVernon Mauery } 20735f0ce03SVernon Mauery 20835f0ce03SVernon Mauery static struct sysdev_class class_rtl = { 20935f0ce03SVernon Mauery .name = "ibm_rtl", 21035f0ce03SVernon Mauery }; 21135f0ce03SVernon Mauery 21235f0ce03SVernon Mauery static SYSDEV_CLASS_ATTR(version, S_IRUGO, rtl_show_version, NULL); 21335f0ce03SVernon Mauery static SYSDEV_CLASS_ATTR(state, 0600, rtl_show_state, rtl_set_state); 21435f0ce03SVernon Mauery 21535f0ce03SVernon Mauery static struct sysdev_class_attribute *rtl_attributes[] = { 21635f0ce03SVernon Mauery &attr_version, 21735f0ce03SVernon Mauery &attr_state, 21835f0ce03SVernon Mauery NULL 21935f0ce03SVernon Mauery }; 22035f0ce03SVernon Mauery 22135f0ce03SVernon Mauery 22235f0ce03SVernon Mauery static int rtl_setup_sysfs(void) { 22335f0ce03SVernon Mauery int ret, i; 22435f0ce03SVernon Mauery ret = sysdev_class_register(&class_rtl); 22535f0ce03SVernon Mauery 22635f0ce03SVernon Mauery if (!ret) { 22735f0ce03SVernon Mauery for (i = 0; rtl_attributes[i]; i ++) 22835f0ce03SVernon Mauery sysdev_class_create_file(&class_rtl, rtl_attributes[i]); 22935f0ce03SVernon Mauery } 23035f0ce03SVernon Mauery return ret; 23135f0ce03SVernon Mauery } 23235f0ce03SVernon Mauery 23335f0ce03SVernon Mauery static void rtl_teardown_sysfs(void) { 23435f0ce03SVernon Mauery int i; 23535f0ce03SVernon Mauery for (i = 0; rtl_attributes[i]; i ++) 23635f0ce03SVernon Mauery sysdev_class_remove_file(&class_rtl, rtl_attributes[i]); 23735f0ce03SVernon Mauery sysdev_class_unregister(&class_rtl); 23835f0ce03SVernon Mauery } 23935f0ce03SVernon Mauery 24035f0ce03SVernon Mauery 24135f0ce03SVernon Mauery static struct dmi_system_id __initdata ibm_rtl_dmi_table[] = { 242a2262260SVernon Mauery { \ 243a2262260SVernon Mauery .matches = { \ 244a2262260SVernon Mauery DMI_MATCH(DMI_SYS_VENDOR, "IBM"), \ 245a2262260SVernon Mauery }, \ 246a2262260SVernon Mauery }, 24735f0ce03SVernon Mauery { } 24835f0ce03SVernon Mauery }; 24935f0ce03SVernon Mauery 25035f0ce03SVernon Mauery static int __init ibm_rtl_init(void) { 25135f0ce03SVernon Mauery unsigned long ebda_addr, ebda_size; 25235f0ce03SVernon Mauery unsigned int ebda_kb; 25335f0ce03SVernon Mauery int ret = -ENODEV, i; 25435f0ce03SVernon Mauery 25535f0ce03SVernon Mauery if (force) 256*323623a7SJoe Perches pr_warn("module loaded by force\n"); 25735f0ce03SVernon Mauery /* first ensure that we are running on IBM HW */ 2581d37db77SVernon Mauery else if (efi_enabled || !dmi_check_system(ibm_rtl_dmi_table)) 25935f0ce03SVernon Mauery return -ENODEV; 26035f0ce03SVernon Mauery 26135f0ce03SVernon Mauery /* Get the address for the Extended BIOS Data Area */ 26235f0ce03SVernon Mauery ebda_addr = get_bios_ebda(); 26335f0ce03SVernon Mauery if (!ebda_addr) { 26435f0ce03SVernon Mauery RTL_DEBUG("no BIOS EBDA found\n"); 26535f0ce03SVernon Mauery return -ENODEV; 26635f0ce03SVernon Mauery } 26735f0ce03SVernon Mauery 26835f0ce03SVernon Mauery ebda_map = ioremap(ebda_addr, 4); 26935f0ce03SVernon Mauery if (!ebda_map) 27035f0ce03SVernon Mauery return -ENOMEM; 27135f0ce03SVernon Mauery 27235f0ce03SVernon Mauery /* First word in the EDBA is the Size in KB */ 27335f0ce03SVernon Mauery ebda_kb = ioread16(ebda_map); 27435f0ce03SVernon Mauery RTL_DEBUG("EBDA is %d kB\n", ebda_kb); 27535f0ce03SVernon Mauery 27635f0ce03SVernon Mauery if (ebda_kb == 0) 27735f0ce03SVernon Mauery goto out; 27835f0ce03SVernon Mauery 27935f0ce03SVernon Mauery iounmap(ebda_map); 28035f0ce03SVernon Mauery ebda_size = ebda_kb*1024; 28135f0ce03SVernon Mauery 28235f0ce03SVernon Mauery /* Remap the whole table */ 28335f0ce03SVernon Mauery ebda_map = ioremap(ebda_addr, ebda_size); 28435f0ce03SVernon Mauery if (!ebda_map) 28535f0ce03SVernon Mauery return -ENOMEM; 28635f0ce03SVernon Mauery 28735f0ce03SVernon Mauery /* search for the _RTL_ signature at the start of the table */ 28835f0ce03SVernon Mauery for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) { 28935f0ce03SVernon Mauery struct ibm_rtl_table __iomem * tmp; 29035f0ce03SVernon Mauery tmp = (struct ibm_rtl_table __iomem *) (ebda_map+i); 29135f0ce03SVernon Mauery if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) { 29235f0ce03SVernon Mauery phys_addr_t addr; 29335f0ce03SVernon Mauery unsigned int plen; 2943a35125fSJoe Perches RTL_DEBUG("found RTL_SIGNATURE at %p\n", tmp); 29535f0ce03SVernon Mauery rtl_table = tmp; 29635f0ce03SVernon Mauery /* The address, value, width and offset are platform 29735f0ce03SVernon Mauery * dependent and found in the ibm_rtl_table */ 29835f0ce03SVernon Mauery rtl_cmd_width = ioread8(&rtl_table->cmd_granularity); 29935f0ce03SVernon Mauery rtl_cmd_type = ioread8(&rtl_table->cmd_address_type); 30035f0ce03SVernon Mauery RTL_DEBUG("rtl_cmd_width = %u, rtl_cmd_type = %u\n", 30135f0ce03SVernon Mauery rtl_cmd_width, rtl_cmd_type); 30235f0ce03SVernon Mauery addr = ioread32(&rtl_table->cmd_port_address); 303c72b844eSRandy Dunlap RTL_DEBUG("addr = %#llx\n", (unsigned long long)addr); 30435f0ce03SVernon Mauery plen = rtl_cmd_width/sizeof(char); 30535f0ce03SVernon Mauery rtl_cmd_addr = rtl_port_map(addr, plen); 3063a35125fSJoe Perches RTL_DEBUG("rtl_cmd_addr = %p\n", rtl_cmd_addr); 30735f0ce03SVernon Mauery if (!rtl_cmd_addr) { 30835f0ce03SVernon Mauery ret = -ENOMEM; 30935f0ce03SVernon Mauery break; 31035f0ce03SVernon Mauery } 31135f0ce03SVernon Mauery ret = rtl_setup_sysfs(); 31235f0ce03SVernon Mauery break; 31335f0ce03SVernon Mauery } 31435f0ce03SVernon Mauery } 31535f0ce03SVernon Mauery 31635f0ce03SVernon Mauery out: 31735f0ce03SVernon Mauery if (ret) { 31835f0ce03SVernon Mauery iounmap(ebda_map); 31935f0ce03SVernon Mauery rtl_port_unmap(rtl_cmd_addr); 32035f0ce03SVernon Mauery } 32135f0ce03SVernon Mauery 32235f0ce03SVernon Mauery return ret; 32335f0ce03SVernon Mauery } 32435f0ce03SVernon Mauery 32535f0ce03SVernon Mauery static void __exit ibm_rtl_exit(void) 32635f0ce03SVernon Mauery { 32735f0ce03SVernon Mauery if (rtl_table) { 32835f0ce03SVernon Mauery RTL_DEBUG("cleaning up"); 32935f0ce03SVernon Mauery /* do not leave the machine in SMI-free mode */ 33035f0ce03SVernon Mauery ibm_rtl_write(0); 33135f0ce03SVernon Mauery /* unmap, unlink and remove all traces */ 33235f0ce03SVernon Mauery rtl_teardown_sysfs(); 33335f0ce03SVernon Mauery iounmap(ebda_map); 33435f0ce03SVernon Mauery rtl_port_unmap(rtl_cmd_addr); 33535f0ce03SVernon Mauery } 33635f0ce03SVernon Mauery } 33735f0ce03SVernon Mauery 33835f0ce03SVernon Mauery module_init(ibm_rtl_init); 33935f0ce03SVernon Mauery module_exit(ibm_rtl_exit); 340