^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 1) // SPDX-License-Identifier: GPL-2.0-only
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 2) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 3) * Linux driver for SSFDC Flash Translation Layer (Read only)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 4) * © 2005 Eptar srl
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 5) * Author: Claudio Lanconelli <lanconelli.claudio@eptar.com>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 6) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 7) * Based on NTFL and MTDBLOCK_RO drivers
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 8) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 9)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 10) #include <linux/kernel.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 11) #include <linux/module.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 12) #include <linux/init.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 13) #include <linux/slab.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 14) #include <linux/hdreg.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 15) #include <linux/mtd/mtd.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 16) #include <linux/mtd/rawnand.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 17) #include <linux/mtd/blktrans.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 18)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 19) struct ssfdcr_record {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 20) struct mtd_blktrans_dev mbd;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 21) int usecount;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 22) unsigned char heads;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 23) unsigned char sectors;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 24) unsigned short cylinders;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 25) int cis_block; /* block n. containing CIS/IDI */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 26) int erase_size; /* phys_block_size */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 27) unsigned short *logic_block_map; /* all zones (max 8192 phys blocks on
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 28) the 128MiB) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 29) int map_len; /* n. phys_blocks on the card */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 30) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 31)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 32) #define SSFDCR_MAJOR 257
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 33) #define SSFDCR_PARTN_BITS 3
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 34)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 35) #define SECTOR_SIZE 512
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 36) #define SECTOR_SHIFT 9
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 37) #define OOB_SIZE 16
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 38)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 39) #define MAX_LOGIC_BLK_PER_ZONE 1000
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 40) #define MAX_PHYS_BLK_PER_ZONE 1024
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 41)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 42) #define KiB(x) ( (x) * 1024L )
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 43) #define MiB(x) ( KiB(x) * 1024L )
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 44)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 45) /** CHS Table
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 46) 1MiB 2MiB 4MiB 8MiB 16MiB 32MiB 64MiB 128MiB
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 47) NCylinder 125 125 250 250 500 500 500 500
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 48) NHead 4 4 4 4 4 8 8 16
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 49) NSector 4 8 8 16 16 16 32 32
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 50) SumSector 2,000 4,000 8,000 16,000 32,000 64,000 128,000 256,000
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 51) SectorSize 512 512 512 512 512 512 512 512
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 52) **/
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 53)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 54) typedef struct {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 55) unsigned long size;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 56) unsigned short cyl;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 57) unsigned char head;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 58) unsigned char sec;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 59) } chs_entry_t;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 60)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 61) /* Must be ordered by size */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 62) static const chs_entry_t chs_table[] = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 63) { MiB( 1), 125, 4, 4 },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 64) { MiB( 2), 125, 4, 8 },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 65) { MiB( 4), 250, 4, 8 },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 66) { MiB( 8), 250, 4, 16 },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 67) { MiB( 16), 500, 4, 16 },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 68) { MiB( 32), 500, 8, 16 },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 69) { MiB( 64), 500, 8, 32 },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 70) { MiB(128), 500, 16, 32 },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 71) { 0 },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 72) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 73)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 74) static int get_chs(unsigned long size, unsigned short *cyl, unsigned char *head,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 75) unsigned char *sec)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 76) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 77) int k;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 78) int found = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 79)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 80) k = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 81) while (chs_table[k].size > 0 && size > chs_table[k].size)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 82) k++;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 83)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 84) if (chs_table[k].size > 0) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 85) if (cyl)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 86) *cyl = chs_table[k].cyl;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 87) if (head)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 88) *head = chs_table[k].head;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 89) if (sec)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 90) *sec = chs_table[k].sec;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 91) found = 1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 92) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 93)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 94) return found;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 95) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 96)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 97) /* These bytes are the signature for the CIS/IDI sector */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 98) static const uint8_t cis_numbers[] = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 99) 0x01, 0x03, 0xD9, 0x01, 0xFF, 0x18, 0x02, 0xDF, 0x01, 0x20
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102) /* Read and check for a valid CIS sector */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103) static int get_valid_cis_sector(struct mtd_info *mtd)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105) int ret, k, cis_sector;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106) size_t retlen;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107) loff_t offset;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108) uint8_t *sect_buf;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110) cis_sector = -1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112) sect_buf = kmalloc(SECTOR_SIZE, GFP_KERNEL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113) if (!sect_buf)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114) goto out;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117) * Look for CIS/IDI sector on the first GOOD block (give up after 4 bad
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118) * blocks). If the first good block doesn't contain CIS number the flash
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119) * is not SSFDC formatted
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121) for (k = 0, offset = 0; k < 4; k++, offset += mtd->erasesize) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) if (mtd_block_isbad(mtd, offset)) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123) ret = mtd_read(mtd, offset, SECTOR_SIZE, &retlen,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124) sect_buf);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126) /* CIS pattern match on the sector buffer */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 127) if (ret < 0 || retlen != SECTOR_SIZE) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 128) printk(KERN_WARNING
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 129) "SSFDC_RO:can't read CIS/IDI sector\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 130) } else if (!memcmp(sect_buf, cis_numbers,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 131) sizeof(cis_numbers))) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 132) /* Found */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 133) cis_sector = (int)(offset >> SECTOR_SHIFT);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 134) } else {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 135) pr_debug("SSFDC_RO: CIS/IDI sector not found"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 136) " on %s (mtd%d)\n", mtd->name,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 137) mtd->index);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 138) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 139) break;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 140) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 141) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 142)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 143) kfree(sect_buf);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 144) out:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 145) return cis_sector;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 146) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 147)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 148) /* Read physical sector (wrapper to MTD_READ) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 149) static int read_physical_sector(struct mtd_info *mtd, uint8_t *sect_buf,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 150) int sect_no)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 151) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 152) int ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 153) size_t retlen;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 154) loff_t offset = (loff_t)sect_no << SECTOR_SHIFT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 155)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 156) ret = mtd_read(mtd, offset, SECTOR_SIZE, &retlen, sect_buf);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 157) if (ret < 0 || retlen != SECTOR_SIZE)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 158) return -1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 159)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 160) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 161) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 162)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 163) /* Read redundancy area (wrapper to MTD_READ_OOB */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 164) static int read_raw_oob(struct mtd_info *mtd, loff_t offs, uint8_t *buf)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 165) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 166) struct mtd_oob_ops ops;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 167) int ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 168)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 169) ops.mode = MTD_OPS_RAW;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 170) ops.ooboffs = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 171) ops.ooblen = OOB_SIZE;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 172) ops.oobbuf = buf;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 173) ops.datbuf = NULL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 174)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 175) ret = mtd_read_oob(mtd, offs, &ops);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 176) if (ret < 0 || ops.oobretlen != OOB_SIZE)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 177) return -1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 178)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 179) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 180) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 181)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 182) /* Parity calculator on a word of n bit size */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 183) static int get_parity(int number, int size)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 184) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 185) int k;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 186) int parity;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 187)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 188) parity = 1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 189) for (k = 0; k < size; k++) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 190) parity += (number >> k);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 191) parity &= 1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 192) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 193) return parity;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 194) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 195)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 196) /* Read and validate the logical block address field stored in the OOB */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 197) static int get_logical_address(uint8_t *oob_buf)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 198) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 199) int block_address, parity;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 200) int offset[2] = {6, 11}; /* offset of the 2 address fields within OOB */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 201) int j;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 202) int ok = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 203)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 204) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 205) * Look for the first valid logical address
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 206) * Valid address has fixed pattern on most significant bits and
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 207) * parity check
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 208) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 209) for (j = 0; j < ARRAY_SIZE(offset); j++) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 210) block_address = ((int)oob_buf[offset[j]] << 8) |
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 211) oob_buf[offset[j]+1];
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 212)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 213) /* Check for the signature bits in the address field (MSBits) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 214) if ((block_address & ~0x7FF) == 0x1000) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 215) parity = block_address & 0x01;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 216) block_address &= 0x7FF;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 217) block_address >>= 1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 218)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 219) if (get_parity(block_address, 10) != parity) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 220) pr_debug("SSFDC_RO: logical address field%d"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 221) "parity error(0x%04X)\n", j+1,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 222) block_address);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 223) } else {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 224) ok = 1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 225) break;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 226) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 227) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 228) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 229)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 230) if (!ok)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 231) block_address = -2;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 232)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 233) pr_debug("SSFDC_RO: get_logical_address() %d\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 234) block_address);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 235)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 236) return block_address;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 237) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 238)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 239) /* Build the logic block map */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 240) static int build_logical_block_map(struct ssfdcr_record *ssfdc)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 241) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 242) unsigned long offset;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 243) uint8_t oob_buf[OOB_SIZE];
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 244) int ret, block_address, phys_block;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 245) struct mtd_info *mtd = ssfdc->mbd.mtd;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 246)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 247) pr_debug("SSFDC_RO: build_block_map() nblks=%d (%luK)\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 248) ssfdc->map_len,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 249) (unsigned long)ssfdc->map_len * ssfdc->erase_size / 1024);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 250)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 251) /* Scan every physical block, skip CIS block */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 252) for (phys_block = ssfdc->cis_block + 1; phys_block < ssfdc->map_len;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 253) phys_block++) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 254) offset = (unsigned long)phys_block * ssfdc->erase_size;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 255) if (mtd_block_isbad(mtd, offset))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 256) continue; /* skip bad blocks */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 257)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 258) ret = read_raw_oob(mtd, offset, oob_buf);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 259) if (ret < 0) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 260) pr_debug("SSFDC_RO: mtd read_oob() failed at %lu\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 261) offset);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 262) return -1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 263) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 264) block_address = get_logical_address(oob_buf);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 265)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 266) /* Skip invalid addresses */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 267) if (block_address >= 0 &&
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 268) block_address < MAX_LOGIC_BLK_PER_ZONE) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 269) int zone_index;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 270)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 271) zone_index = phys_block / MAX_PHYS_BLK_PER_ZONE;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 272) block_address += zone_index * MAX_LOGIC_BLK_PER_ZONE;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 273) ssfdc->logic_block_map[block_address] =
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 274) (unsigned short)phys_block;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 275)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 276) pr_debug("SSFDC_RO: build_block_map() phys_block=%d,"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 277) "logic_block_addr=%d, zone=%d\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 278) phys_block, block_address, zone_index);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 279) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 280) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 281) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 282) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 283)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 284) static void ssfdcr_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 285) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 286) struct ssfdcr_record *ssfdc;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 287) int cis_sector;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 288)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 289) /* Check for small page NAND flash */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 290) if (!mtd_type_is_nand(mtd) || mtd->oobsize != OOB_SIZE ||
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 291) mtd->size > UINT_MAX)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 292) return;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 293)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 294) /* Check for SSDFC format by reading CIS/IDI sector */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 295) cis_sector = get_valid_cis_sector(mtd);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 296) if (cis_sector == -1)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 297) return;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 298)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 299) ssfdc = kzalloc(sizeof(struct ssfdcr_record), GFP_KERNEL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 300) if (!ssfdc)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 301) return;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 302)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 303) ssfdc->mbd.mtd = mtd;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 304) ssfdc->mbd.devnum = -1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 305) ssfdc->mbd.tr = tr;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 306) ssfdc->mbd.readonly = 1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 307)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 308) ssfdc->cis_block = cis_sector / (mtd->erasesize >> SECTOR_SHIFT);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 309) ssfdc->erase_size = mtd->erasesize;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 310) ssfdc->map_len = (u32)mtd->size / mtd->erasesize;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 311)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 312) pr_debug("SSFDC_RO: cis_block=%d,erase_size=%d,map_len=%d,n_zones=%d\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 313) ssfdc->cis_block, ssfdc->erase_size, ssfdc->map_len,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 314) DIV_ROUND_UP(ssfdc->map_len, MAX_PHYS_BLK_PER_ZONE));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 315)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 316) /* Set geometry */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 317) ssfdc->heads = 16;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 318) ssfdc->sectors = 32;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 319) get_chs(mtd->size, NULL, &ssfdc->heads, &ssfdc->sectors);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 320) ssfdc->cylinders = (unsigned short)(((u32)mtd->size >> SECTOR_SHIFT) /
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 321) ((long)ssfdc->sectors * (long)ssfdc->heads));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 322)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 323) pr_debug("SSFDC_RO: using C:%d H:%d S:%d == %ld sects\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 324) ssfdc->cylinders, ssfdc->heads , ssfdc->sectors,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 325) (long)ssfdc->cylinders * (long)ssfdc->heads *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 326) (long)ssfdc->sectors);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 327)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 328) ssfdc->mbd.size = (long)ssfdc->heads * (long)ssfdc->cylinders *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 329) (long)ssfdc->sectors;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 330)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 331) /* Allocate logical block map */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 332) ssfdc->logic_block_map =
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 333) kmalloc_array(ssfdc->map_len,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 334) sizeof(ssfdc->logic_block_map[0]), GFP_KERNEL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 335) if (!ssfdc->logic_block_map)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 336) goto out_err;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 337) memset(ssfdc->logic_block_map, 0xff, sizeof(ssfdc->logic_block_map[0]) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 338) ssfdc->map_len);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 339)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 340) /* Build logical block map */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 341) if (build_logical_block_map(ssfdc) < 0)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 342) goto out_err;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 343)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 344) /* Register device + partitions */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 345) if (add_mtd_blktrans_dev(&ssfdc->mbd))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 346) goto out_err;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 347)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 348) printk(KERN_INFO "SSFDC_RO: Found ssfdc%c on mtd%d (%s)\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 349) ssfdc->mbd.devnum + 'a', mtd->index, mtd->name);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 350) return;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 351)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 352) out_err:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 353) kfree(ssfdc->logic_block_map);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 354) kfree(ssfdc);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 355) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 356)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 357) static void ssfdcr_remove_dev(struct mtd_blktrans_dev *dev)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 358) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 359) struct ssfdcr_record *ssfdc = (struct ssfdcr_record *)dev;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 360)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 361) pr_debug("SSFDC_RO: remove_dev (i=%d)\n", dev->devnum);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 362)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 363) del_mtd_blktrans_dev(dev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 364) kfree(ssfdc->logic_block_map);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 365) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 366)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 367) static int ssfdcr_readsect(struct mtd_blktrans_dev *dev,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 368) unsigned long logic_sect_no, char *buf)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 369) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 370) struct ssfdcr_record *ssfdc = (struct ssfdcr_record *)dev;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 371) int sectors_per_block, offset, block_address;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 372)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 373) sectors_per_block = ssfdc->erase_size >> SECTOR_SHIFT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 374) offset = (int)(logic_sect_no % sectors_per_block);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 375) block_address = (int)(logic_sect_no / sectors_per_block);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 376)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 377) pr_debug("SSFDC_RO: ssfdcr_readsect(%lu) sec_per_blk=%d, ofst=%d,"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 378) " block_addr=%d\n", logic_sect_no, sectors_per_block, offset,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 379) block_address);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 380)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 381) BUG_ON(block_address >= ssfdc->map_len);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 382)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 383) block_address = ssfdc->logic_block_map[block_address];
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 384)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 385) pr_debug("SSFDC_RO: ssfdcr_readsect() phys_block_addr=%d\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 386) block_address);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 387)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 388) if (block_address < 0xffff) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 389) unsigned long sect_no;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 390)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 391) sect_no = (unsigned long)block_address * sectors_per_block +
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 392) offset;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 393)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 394) pr_debug("SSFDC_RO: ssfdcr_readsect() phys_sect_no=%lu\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 395) sect_no);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 396)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 397) if (read_physical_sector(ssfdc->mbd.mtd, buf, sect_no) < 0)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 398) return -EIO;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 399) } else {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 400) memset(buf, 0xff, SECTOR_SIZE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 401) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 402)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 403) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 404) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 405)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 406) static int ssfdcr_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 407) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 408) struct ssfdcr_record *ssfdc = (struct ssfdcr_record *)dev;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 409)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 410) pr_debug("SSFDC_RO: ssfdcr_getgeo() C=%d, H=%d, S=%d\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 411) ssfdc->cylinders, ssfdc->heads, ssfdc->sectors);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 412)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 413) geo->heads = ssfdc->heads;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 414) geo->sectors = ssfdc->sectors;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 415) geo->cylinders = ssfdc->cylinders;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 416)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 417) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 418) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 419)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 420) /****************************************************************************
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 421) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 422) * Module stuff
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 423) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 424) ****************************************************************************/
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 425)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 426) static struct mtd_blktrans_ops ssfdcr_tr = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 427) .name = "ssfdc",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 428) .major = SSFDCR_MAJOR,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 429) .part_bits = SSFDCR_PARTN_BITS,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 430) .blksize = SECTOR_SIZE,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 431) .getgeo = ssfdcr_getgeo,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 432) .readsect = ssfdcr_readsect,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 433) .add_mtd = ssfdcr_add_mtd,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 434) .remove_dev = ssfdcr_remove_dev,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 435) .owner = THIS_MODULE,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 436) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 437)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 438) static int __init init_ssfdcr(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 439) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 440) printk(KERN_INFO "SSFDC read-only Flash Translation layer\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 441)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 442) return register_mtd_blktrans(&ssfdcr_tr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 443) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 444)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 445) static void __exit cleanup_ssfdcr(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 446) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 447) deregister_mtd_blktrans(&ssfdcr_tr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 448) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 449)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 450) module_init(init_ssfdcr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 451) module_exit(cleanup_ssfdcr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 452)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 453) MODULE_LICENSE("GPL");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 454) MODULE_AUTHOR("Claudio Lanconelli <lanconelli.claudio@eptar.com>");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 455) MODULE_DESCRIPTION("Flash Translation Layer for read-only SSFDC SmartMedia card");