Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame^] | 1 | /* elan-104nc.c -- MTD map driver for Arcom Control Systems ELAN-104NC |
| 2 | |
| 3 | Copyright (C) 2000 Arcom Control System Ltd |
| 4 | |
| 5 | This program is free software; you can redistribute it and/or modify |
| 6 | it under the terms of the GNU General Public License as published by |
| 7 | the Free Software Foundation; either version 2 of the License, or |
| 8 | (at your option) any later version. |
| 9 | |
| 10 | This program is distributed in the hope that it will be useful, |
| 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | GNU General Public License for more details. |
| 14 | |
| 15 | You should have received a copy of the GNU General Public License |
| 16 | along with this program; if not, write to the Free Software |
| 17 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA |
| 18 | |
| 19 | $Id: elan-104nc.c,v 1.25 2004/11/28 09:40:39 dwmw2 Exp $ |
| 20 | |
| 21 | The ELAN-104NC has up to 8 Mibyte of Intel StrataFlash (28F320/28F640) in x16 |
| 22 | mode. This drivers uses the CFI probe and Intel Extended Command Set drivers. |
| 23 | |
| 24 | The flash is accessed as follows: |
| 25 | |
| 26 | 32 kbyte memory window at 0xb0000-0xb7fff |
| 27 | |
| 28 | 16 bit I/O port (0x22) for some sort of paging. |
| 29 | |
| 30 | The single flash device is divided into 3 partition which appear as separate |
| 31 | MTD devices. |
| 32 | |
| 33 | Linux thinks that the I/O port is used by the PIC and hence check_region() will |
| 34 | always fail. So we don't do it. I just hope it doesn't break anything. |
| 35 | */ |
| 36 | #include <linux/module.h> |
| 37 | #include <linux/slab.h> |
| 38 | #include <linux/ioport.h> |
| 39 | #include <linux/init.h> |
| 40 | #include <asm/io.h> |
| 41 | |
| 42 | #include <linux/mtd/map.h> |
| 43 | #include <linux/mtd/mtd.h> |
| 44 | #include <linux/mtd/partitions.h> |
| 45 | |
| 46 | #define WINDOW_START 0xb0000 |
| 47 | /* Number of bits in offset. */ |
| 48 | #define WINDOW_SHIFT 15 |
| 49 | #define WINDOW_LENGTH (1 << WINDOW_SHIFT) |
| 50 | /* The bits for the offset into the window. */ |
| 51 | #define WINDOW_MASK (WINDOW_LENGTH-1) |
| 52 | #define PAGE_IO 0x22 |
| 53 | #define PAGE_IO_SIZE 2 |
| 54 | |
| 55 | static volatile int page_in_window = -1; // Current page in window. |
| 56 | static void __iomem *iomapadr; |
| 57 | static DEFINE_SPINLOCK(elan_104nc_spin); |
| 58 | |
| 59 | /* partition_info gives details on the logical partitions that the split the |
| 60 | * single flash device into. If the size if zero we use up to the end of the |
| 61 | * device. */ |
| 62 | static struct mtd_partition partition_info[]={ |
| 63 | { .name = "ELAN-104NC flash boot partition", |
| 64 | .offset = 0, |
| 65 | .size = 640*1024 }, |
| 66 | { .name = "ELAN-104NC flash partition 1", |
| 67 | .offset = 640*1024, |
| 68 | .size = 896*1024 }, |
| 69 | { .name = "ELAN-104NC flash partition 2", |
| 70 | .offset = (640+896)*1024 } |
| 71 | }; |
| 72 | #define NUM_PARTITIONS (sizeof(partition_info)/sizeof(partition_info[0])) |
| 73 | |
| 74 | /* |
| 75 | * If no idea what is going on here. This is taken from the FlashFX stuff. |
| 76 | */ |
| 77 | #define ROMCS 1 |
| 78 | |
| 79 | static inline void elan_104nc_setup(void) |
| 80 | { |
| 81 | u16 t; |
| 82 | |
| 83 | outw( 0x0023 + ROMCS*2, PAGE_IO ); |
| 84 | t=inb( PAGE_IO+1 ); |
| 85 | |
| 86 | t=(t & 0xf9) | 0x04; |
| 87 | |
| 88 | outw( ((0x0023 + ROMCS*2) | (t << 8)), PAGE_IO ); |
| 89 | } |
| 90 | |
| 91 | static inline void elan_104nc_page(struct map_info *map, unsigned long ofs) |
| 92 | { |
| 93 | unsigned long page = ofs >> WINDOW_SHIFT; |
| 94 | |
| 95 | if( page!=page_in_window ) { |
| 96 | int cmd1; |
| 97 | int cmd2; |
| 98 | |
| 99 | cmd1=(page & 0x700) + 0x0833 + ROMCS*0x4000; |
| 100 | cmd2=((page & 0xff) << 8) + 0x0032; |
| 101 | |
| 102 | outw( cmd1, PAGE_IO ); |
| 103 | outw( cmd2, PAGE_IO ); |
| 104 | |
| 105 | page_in_window = page; |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | |
| 110 | static map_word elan_104nc_read16(struct map_info *map, unsigned long ofs) |
| 111 | { |
| 112 | map_word ret; |
| 113 | spin_lock(&elan_104nc_spin); |
| 114 | elan_104nc_page(map, ofs); |
| 115 | ret.x[0] = readw(iomapadr + (ofs & WINDOW_MASK)); |
| 116 | spin_unlock(&elan_104nc_spin); |
| 117 | return ret; |
| 118 | } |
| 119 | |
| 120 | static void elan_104nc_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len) |
| 121 | { |
| 122 | while (len) { |
| 123 | unsigned long thislen = len; |
| 124 | if (len > (WINDOW_LENGTH - (from & WINDOW_MASK))) |
| 125 | thislen = WINDOW_LENGTH-(from & WINDOW_MASK); |
| 126 | |
| 127 | spin_lock(&elan_104nc_spin); |
| 128 | elan_104nc_page(map, from); |
| 129 | memcpy_fromio(to, iomapadr + (from & WINDOW_MASK), thislen); |
| 130 | spin_unlock(&elan_104nc_spin); |
| 131 | to += thislen; |
| 132 | from += thislen; |
| 133 | len -= thislen; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | static void elan_104nc_write16(struct map_info *map, map_word d, unsigned long adr) |
| 138 | { |
| 139 | spin_lock(&elan_104nc_spin); |
| 140 | elan_104nc_page(map, adr); |
| 141 | writew(d.x[0], iomapadr + (adr & WINDOW_MASK)); |
| 142 | spin_unlock(&elan_104nc_spin); |
| 143 | } |
| 144 | |
| 145 | static void elan_104nc_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len) |
| 146 | { |
| 147 | while(len) { |
| 148 | unsigned long thislen = len; |
| 149 | if (len > (WINDOW_LENGTH - (to & WINDOW_MASK))) |
| 150 | thislen = WINDOW_LENGTH-(to & WINDOW_MASK); |
| 151 | |
| 152 | spin_lock(&elan_104nc_spin); |
| 153 | elan_104nc_page(map, to); |
| 154 | memcpy_toio(iomapadr + (to & WINDOW_MASK), from, thislen); |
| 155 | spin_unlock(&elan_104nc_spin); |
| 156 | to += thislen; |
| 157 | from += thislen; |
| 158 | len -= thislen; |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | static struct map_info elan_104nc_map = { |
| 163 | .name = "ELAN-104NC flash", |
| 164 | .phys = NO_XIP, |
| 165 | .size = 8*1024*1024, /* this must be set to a maximum possible amount |
| 166 | of flash so the cfi probe routines find all |
| 167 | the chips */ |
| 168 | .bankwidth = 2, |
| 169 | .read = elan_104nc_read16, |
| 170 | .copy_from = elan_104nc_copy_from, |
| 171 | .write = elan_104nc_write16, |
| 172 | .copy_to = elan_104nc_copy_to |
| 173 | }; |
| 174 | |
| 175 | /* MTD device for all of the flash. */ |
| 176 | static struct mtd_info *all_mtd; |
| 177 | |
| 178 | static void cleanup_elan_104nc(void) |
| 179 | { |
| 180 | if( all_mtd ) { |
| 181 | del_mtd_partitions( all_mtd ); |
| 182 | map_destroy( all_mtd ); |
| 183 | } |
| 184 | |
| 185 | iounmap(iomapadr); |
| 186 | } |
| 187 | |
| 188 | static int __init init_elan_104nc(void) |
| 189 | { |
| 190 | /* Urg! We use I/O port 0x22 without request_region()ing it, |
| 191 | because it's already allocated to the PIC. */ |
| 192 | |
| 193 | iomapadr = ioremap(WINDOW_START, WINDOW_LENGTH); |
| 194 | if (!iomapadr) { |
| 195 | printk( KERN_ERR"%s: failed to ioremap memory region\n", |
| 196 | elan_104nc_map.name ); |
| 197 | return -EIO; |
| 198 | } |
| 199 | |
| 200 | printk( KERN_INFO"%s: IO:0x%x-0x%x MEM:0x%x-0x%x\n", |
| 201 | elan_104nc_map.name, |
| 202 | PAGE_IO, PAGE_IO+PAGE_IO_SIZE-1, |
| 203 | WINDOW_START, WINDOW_START+WINDOW_LENGTH-1 ); |
| 204 | |
| 205 | elan_104nc_setup(); |
| 206 | |
| 207 | /* Probe for chip. */ |
| 208 | all_mtd = do_map_probe("cfi_probe", &elan_104nc_map ); |
| 209 | if( !all_mtd ) { |
| 210 | cleanup_elan_104nc(); |
| 211 | return -ENXIO; |
| 212 | } |
| 213 | |
| 214 | all_mtd->owner = THIS_MODULE; |
| 215 | |
| 216 | /* Create MTD devices for each partition. */ |
| 217 | add_mtd_partitions( all_mtd, partition_info, NUM_PARTITIONS ); |
| 218 | |
| 219 | return 0; |
| 220 | } |
| 221 | |
| 222 | module_init(init_elan_104nc); |
| 223 | module_exit(cleanup_elan_104nc); |
| 224 | |
| 225 | |
| 226 | MODULE_LICENSE("GPL"); |
| 227 | MODULE_AUTHOR("Arcom Control Systems Ltd."); |
| 228 | MODULE_DESCRIPTION("MTD map driver for Arcom Control Systems ELAN-104NC"); |