| /* |
| * ich2rom.c |
| * |
| * Normal mappings of chips in physical memory |
| * $Id: ich2rom.c,v 1.7 2003/05/21 12:45:18 dwmw2 Exp $ |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <asm/io.h> |
| #include <linux/mtd/mtd.h> |
| #include <linux/mtd/map.h> |
| #include <linux/config.h> |
| #include <linux/pci.h> |
| #include <linux/pci_ids.h> |
| |
| #define RESERVE_MEM_REGION 0 |
| |
| #define ICH2_FWH_REGION_START 0xFF000000UL |
| #define ICH2_FWH_REGION_SIZE 0x01000000UL |
| #define BIOS_CNTL 0x4e |
| #define FWH_DEC_EN1 0xE3 |
| #define FWH_DEC_EN2 0xF0 |
| #define FWH_SEL1 0xE8 |
| #define FWH_SEL2 0xEE |
| |
| struct ich2rom_map_info { |
| struct map_info map; |
| struct mtd_info *mtd; |
| unsigned long window_addr; |
| }; |
| |
| static inline unsigned long addr(struct map_info *map, unsigned long ofs) |
| { |
| unsigned long offset; |
| offset = ((8*1024*1024) - map->size) + ofs; |
| if (offset >= (4*1024*1024)) { |
| offset += 0x400000; |
| } |
| return map->map_priv_1 + 0x400000 + offset; |
| } |
| |
| static inline unsigned long dbg_addr(struct map_info *map, unsigned long addr) |
| { |
| return addr - map->map_priv_1 + ICH2_FWH_REGION_START; |
| } |
| |
| static __u8 ich2rom_read8(struct map_info *map, unsigned long ofs) |
| { |
| return __raw_readb(addr(map, ofs)); |
| } |
| |
| static __u16 ich2rom_read16(struct map_info *map, unsigned long ofs) |
| { |
| return __raw_readw(addr(map, ofs)); |
| } |
| |
| static __u32 ich2rom_read32(struct map_info *map, unsigned long ofs) |
| { |
| return __raw_readl(addr(map, ofs)); |
| } |
| |
| static void ich2rom_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len) |
| { |
| memcpy_fromio(to, addr(map, from), len); |
| } |
| |
| static void ich2rom_write8(struct map_info *map, __u8 d, unsigned long ofs) |
| { |
| __raw_writeb(d, addr(map,ofs)); |
| mb(); |
| } |
| |
| static void ich2rom_write16(struct map_info *map, __u16 d, unsigned long ofs) |
| { |
| __raw_writew(d, addr(map, ofs)); |
| mb(); |
| } |
| |
| static void ich2rom_write32(struct map_info *map, __u32 d, unsigned long ofs) |
| { |
| __raw_writel(d, addr(map, ofs)); |
| mb(); |
| } |
| |
| static void ich2rom_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len) |
| { |
| memcpy_toio(addr(map, to), from, len); |
| } |
| |
| static struct ich2rom_map_info ich2rom_map = { |
| .map = { |
| .name = "ICH2 rom", |
| .phys = NO_XIP, |
| .size = 0, |
| .buswidth = 1, |
| .read8 = ich2rom_read8, |
| .read16 = ich2rom_read16, |
| .read32 = ich2rom_read32, |
| .copy_from = ich2rom_copy_from, |
| .write8 = ich2rom_write8, |
| .write16 = ich2rom_write16, |
| .write32 = ich2rom_write32, |
| .copy_to = ich2rom_copy_to, |
| /* Firmware hubs only use vpp when being programmed |
| * in a factory setting. So in place programming |
| * needs to use a different method. |
| */ |
| }, |
| .mtd = NULL, |
| .window_addr = 0, |
| }; |
| |
| enum fwh_lock_state { |
| FWH_DENY_WRITE = 1, |
| FWH_IMMUTABLE = 2, |
| FWH_DENY_READ = 4, |
| }; |
| |
| static int ich2rom_set_lock_state(struct mtd_info *mtd, loff_t ofs, size_t len, |
| enum fwh_lock_state state) |
| { |
| struct map_info *map = mtd->priv; |
| unsigned long start = ofs; |
| unsigned long end = start + len -1; |
| |
| /* FIXME do I need to guard against concurrency here? */ |
| /* round down to 64K boundaries */ |
| start = start & ~0xFFFF; |
| end = end & ~0xFFFF; |
| while (start <= end) { |
| unsigned long ctrl_addr; |
| ctrl_addr = addr(map, start) - 0x400000 + 2; |
| writeb(state, ctrl_addr); |
| start = start + 0x10000; |
| } |
| return 0; |
| } |
| |
| static int ich2rom_lock(struct mtd_info *mtd, loff_t ofs, size_t len) |
| { |
| return ich2rom_set_lock_state(mtd, ofs, len, FWH_DENY_WRITE); |
| } |
| |
| static int ich2rom_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) |
| { |
| return ich2rom_set_lock_state(mtd, ofs, len, 0); |
| } |
| |
| static int __devinit ich2rom_init_one (struct pci_dev *pdev, |
| const struct pci_device_id *ent) |
| { |
| u16 word; |
| struct ich2rom_map_info *info = &ich2rom_map; |
| unsigned long map_size; |
| |
| /* For now I just handle the ich2 and I assume there |
| * are not a lot of resources up at the top of the address |
| * space. It is possible to handle other devices in the |
| * top 16MB but it is very painful. Also since |
| * you can only really attach a FWH to an ICH2 there |
| * a number of simplifications you can make. |
| * |
| * Also you can page firmware hubs if an 8MB window isn't enough |
| * but don't currently handle that case either. |
| */ |
| |
| #if RESERVE_MEM_REGION |
| /* Some boards have this reserved and I haven't found a good work |
| * around to say I know what I'm doing! |
| */ |
| if (!request_mem_region(ICH2_FWH_REGION_START, ICH2_FWH_REGION_SIZE, "ich2rom")) { |
| printk(KERN_ERR "ich2rom: cannot reserve rom window\n"); |
| goto err_out_none; |
| } |
| #endif /* RESERVE_MEM_REGION */ |
| |
| /* Enable writes through the rom window */ |
| pci_read_config_word(pdev, BIOS_CNTL, &word); |
| if (!(word & 1) && (word & (1<<1))) { |
| /* The BIOS will generate an error if I enable |
| * this device, so don't even try. |
| */ |
| printk(KERN_ERR "ich2rom: firmware access control, I can't enable writes\n"); |
| goto err_out_none; |
| } |
| pci_write_config_word(pdev, BIOS_CNTL, word | 1); |
| |
| |
| /* Map the firmware hub into my address space. */ |
| /* Does this use to much virtual address space? */ |
| info->window_addr = (unsigned long)ioremap( |
| ICH2_FWH_REGION_START, ICH2_FWH_REGION_SIZE); |
| if (!info->window_addr) { |
| printk(KERN_ERR "Failed to ioremap\n"); |
| goto err_out_free_mmio_region; |
| } |
| |
| /* For now assume the firmware has setup all relevant firmware |
| * windows. We don't have enough information to handle this case |
| * intelligently. |
| */ |
| |
| /* FIXME select the firmware hub and enable a window to it. */ |
| |
| info->mtd = NULL; |
| info->map.map_priv_1 = info->window_addr; |
| |
| map_size = ICH2_FWH_REGION_SIZE; |
| while(!info->mtd && (map_size > 0)) { |
| info->map.size = map_size; |
| info->mtd = do_map_probe("jedec_probe", &ich2rom_map.map); |
| map_size -= 512*1024; |
| } |
| if (!info->mtd) { |
| goto err_out_iounmap; |
| } |
| /* I know I can only be a firmware hub here so put |
| * in the special lock and unlock routines. |
| */ |
| info->mtd->lock = ich2rom_lock; |
| info->mtd->unlock = ich2rom_unlock; |
| |
| info->mtd->owner = THIS_MODULE; |
| add_mtd_device(info->mtd); |
| return 0; |
| |
| err_out_iounmap: |
| iounmap((void *)(info->window_addr)); |
| err_out_free_mmio_region: |
| #if RESERVE_MEM_REGION |
| release_mem_region(ICH2_FWH_REGION_START, ICH2_FWH_REGION_SIZE); |
| #endif |
| err_out_none: |
| return -ENODEV; |
| } |
| |
| |
| static void __devexit ich2rom_remove_one (struct pci_dev *pdev) |
| { |
| struct ich2rom_map_info *info = &ich2rom_map; |
| u16 word; |
| |
| del_mtd_device(info->mtd); |
| map_destroy(info->mtd); |
| info->mtd = NULL; |
| info->map.map_priv_1 = 0; |
| |
| iounmap((void *)(info->window_addr)); |
| info->window_addr = 0; |
| |
| /* Disable writes through the rom window */ |
| pci_read_config_word(pdev, BIOS_CNTL, &word); |
| pci_write_config_word(pdev, BIOS_CNTL, word & ~1); |
| |
| #if RESERVE_MEM_REGION |
| release_mem_region(ICH2_FWH_REGION_START, ICH2_FWH_REGION_SIZE); |
| #endif |
| } |
| |
| static struct pci_device_id ich2rom_pci_tbl[] = { |
| { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, |
| PCI_ANY_ID, PCI_ANY_ID, }, |
| { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, |
| PCI_ANY_ID, PCI_ANY_ID, }, |
| { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, |
| PCI_ANY_ID, PCI_ANY_ID, }, |
| { 0, }, |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, ich2rom_pci_tbl); |
| |
| #if 0 |
| static struct pci_driver ich2rom_driver = { |
| .name = "ich2rom", |
| .id_table = ich2rom_pci_tbl, |
| .probe = ich2rom_init_one, |
| .remove = ich2rom_remove_one, |
| }; |
| #endif |
| |
| static struct pci_dev *mydev; |
| int __init init_ich2rom(void) |
| { |
| struct pci_dev *pdev; |
| struct pci_device_id *id; |
| pdev = NULL; |
| for(id = ich2rom_pci_tbl; id->vendor; id++) { |
| pdev = pci_find_device(id->vendor, id->device, NULL); |
| if (pdev) { |
| break; |
| } |
| } |
| if (pdev) { |
| mydev = pdev; |
| return ich2rom_init_one(pdev, &ich2rom_pci_tbl[0]); |
| } |
| return -ENXIO; |
| #if 0 |
| return pci_module_init(&ich2rom_driver); |
| #endif |
| } |
| |
| static void __exit cleanup_ich2rom(void) |
| { |
| ich2rom_remove_one(mydev); |
| } |
| |
| module_init(init_ich2rom); |
| module_exit(cleanup_ich2rom); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Eric Biederman <ebiederman@lnxi.com>"); |
| MODULE_DESCRIPTION("MTD map driver for BIOS chips on the ICH2 southbridge"); |