1 /* 2 * lpc_sch.c - LPC interface for Intel Poulsbo SCH 3 * 4 * LPC bridge function of the Intel SCH contains many other 5 * functional units, such as Interrupt controllers, Timers, 6 * Power Management, System Management, GPIO, RTC, and LPC 7 * Configuration Registers. 8 * 9 * Copyright (c) 2010 CompuLab Ltd 10 * Author: Denis Turischev <denis@compulab.co.il> 11 * 12 * This program is free software; you can redistribute it and/or modify 13 * it under the terms of the GNU General Public License 2 as published 14 * by the Free Software Foundation. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program; see the file COPYING. If not, write to 23 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 24 */ 25 26 #include <linux/init.h> 27 #include <linux/kernel.h> 28 #include <linux/module.h> 29 #include <linux/errno.h> 30 #include <linux/acpi.h> 31 #include <linux/pci.h> 32 #include <linux/mfd/core.h> 33 34 #define SMBASE 0x40 35 #define SMBUS_IO_SIZE 64 36 37 #define GPIOBASE 0x44 38 #define GPIO_IO_SIZE 64 39 #define GPIO_IO_SIZE_CENTERTON 128 40 41 #define WDTBASE 0x84 42 #define WDT_IO_SIZE 64 43 44 static struct resource smbus_sch_resource = { 45 .flags = IORESOURCE_IO, 46 }; 47 48 49 static struct resource gpio_sch_resource = { 50 .flags = IORESOURCE_IO, 51 }; 52 53 static struct mfd_cell lpc_sch_cells[] = { 54 { 55 .name = "isch_smbus", 56 .num_resources = 1, 57 .resources = &smbus_sch_resource, 58 }, 59 { 60 .name = "sch_gpio", 61 .num_resources = 1, 62 .resources = &gpio_sch_resource, 63 }, 64 }; 65 66 static struct resource wdt_sch_resource = { 67 .flags = IORESOURCE_IO, 68 }; 69 70 static struct mfd_cell tunnelcreek_cells[] = { 71 { 72 .name = "ie6xx_wdt", 73 .num_resources = 1, 74 .resources = &wdt_sch_resource, 75 }, 76 }; 77 78 static DEFINE_PCI_DEVICE_TABLE(lpc_sch_ids) = { 79 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_SCH_LPC) }, 80 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ITC_LPC) }, 81 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_CENTERTON_ILB) }, 82 { 0, } 83 }; 84 MODULE_DEVICE_TABLE(pci, lpc_sch_ids); 85 86 static int lpc_sch_probe(struct pci_dev *dev, 87 const struct pci_device_id *id) 88 { 89 unsigned int base_addr_cfg; 90 unsigned short base_addr; 91 int i; 92 int ret; 93 94 pci_read_config_dword(dev, SMBASE, &base_addr_cfg); 95 if (!(base_addr_cfg & (1 << 31))) { 96 dev_err(&dev->dev, "Decode of the SMBus I/O range disabled\n"); 97 return -ENODEV; 98 } 99 base_addr = (unsigned short)base_addr_cfg; 100 if (base_addr == 0) { 101 dev_err(&dev->dev, "I/O space for SMBus uninitialized\n"); 102 return -ENODEV; 103 } 104 105 smbus_sch_resource.start = base_addr; 106 smbus_sch_resource.end = base_addr + SMBUS_IO_SIZE - 1; 107 108 pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg); 109 if (!(base_addr_cfg & (1 << 31))) { 110 dev_err(&dev->dev, "Decode of the GPIO I/O range disabled\n"); 111 return -ENODEV; 112 } 113 base_addr = (unsigned short)base_addr_cfg; 114 if (base_addr == 0) { 115 dev_err(&dev->dev, "I/O space for GPIO uninitialized\n"); 116 return -ENODEV; 117 } 118 119 gpio_sch_resource.start = base_addr; 120 121 if (id->device == PCI_DEVICE_ID_INTEL_CENTERTON_ILB) 122 gpio_sch_resource.end = base_addr + GPIO_IO_SIZE_CENTERTON - 1; 123 else 124 gpio_sch_resource.end = base_addr + GPIO_IO_SIZE - 1; 125 126 for (i=0; i < ARRAY_SIZE(lpc_sch_cells); i++) 127 lpc_sch_cells[i].id = id->device; 128 129 ret = mfd_add_devices(&dev->dev, 0, 130 lpc_sch_cells, ARRAY_SIZE(lpc_sch_cells), NULL, 131 0, NULL); 132 if (ret) 133 goto out_dev; 134 135 if (id->device == PCI_DEVICE_ID_INTEL_ITC_LPC 136 || id->device == PCI_DEVICE_ID_INTEL_CENTERTON_ILB) { 137 pci_read_config_dword(dev, WDTBASE, &base_addr_cfg); 138 if (!(base_addr_cfg & (1 << 31))) { 139 dev_err(&dev->dev, "Decode of the WDT I/O range disabled\n"); 140 ret = -ENODEV; 141 goto out_dev; 142 } 143 base_addr = (unsigned short)base_addr_cfg; 144 if (base_addr == 0) { 145 dev_err(&dev->dev, "I/O space for WDT uninitialized\n"); 146 ret = -ENODEV; 147 goto out_dev; 148 } 149 150 wdt_sch_resource.start = base_addr; 151 wdt_sch_resource.end = base_addr + WDT_IO_SIZE - 1; 152 153 for (i = 0; i < ARRAY_SIZE(tunnelcreek_cells); i++) 154 tunnelcreek_cells[i].id = id->device; 155 156 ret = mfd_add_devices(&dev->dev, 0, tunnelcreek_cells, 157 ARRAY_SIZE(tunnelcreek_cells), NULL, 158 0, NULL); 159 } 160 161 return ret; 162 out_dev: 163 mfd_remove_devices(&dev->dev); 164 return ret; 165 } 166 167 static void lpc_sch_remove(struct pci_dev *dev) 168 { 169 mfd_remove_devices(&dev->dev); 170 } 171 172 static struct pci_driver lpc_sch_driver = { 173 .name = "lpc_sch", 174 .id_table = lpc_sch_ids, 175 .probe = lpc_sch_probe, 176 .remove = lpc_sch_remove, 177 }; 178 179 module_pci_driver(lpc_sch_driver); 180 181 MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>"); 182 MODULE_DESCRIPTION("LPC interface for Intel Poulsbo SCH"); 183 MODULE_LICENSE("GPL"); 184