/* $NetBSD: omap2_nand.c,v 1.2.2.2 2019/11/27 13:46:44 martin Exp $ */ /*- * Copyright (c) 2010 Department of Software Engineering, * University of Szeged, Hungary * Copyright (c) 2010 Adam Hoka * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by the Department of Software Engineering, University of Szeged, Hungary * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* Device driver for the NAND controller found in Texas Instruments OMAP2 * and later SOCs. */ #include __KERNEL_RCSID(0, "$NetBSD: omap2_nand.c,v 1.2.2.2 2019/11/27 13:46:44 martin Exp $"); /* TODO move to opt_* */ #undef OMAP2_NAND_HARDWARE_ECC #include #include #include #include #include #include #include #include #include extern struct flash_interface nand_flash_if; extern int flash_print(void *, const char *); /* GPMC_STATUS */ #define WAIT0 __BIT(8) /* active low */ /* GPMC_ECC_CONTROL */ #define ECCCLEAR __BIT(8) #define ECCPOINTER __BITS(3,0) /* GPMC_ECC_CONFIG */ #define ECCALGORITHM __BIT(16) #define ECCCS __BITS(3,1) #define ECC16B __BIT(7) #define ECCENABLE __BIT(0) /* GPMC_ECC_SIZE_CONFIG */ #define ECCSIZE1 __BITS(29,22) /* GPMC_CONFIG1_i */ #define DEVICETYPE __BITS(11,10) #define DEVICESIZE __BITS(13,12) #define MASKEDINT(mask, integer) ((integer) << (ffs(mask) - 1) & mask) /* NAND status register */ #define NAND_WP_BIT __BIT(4) static int omap2_nand_match(device_t, cfdata_t, void *); static void omap2_nand_attach(device_t, device_t, void *); static void omap2_nand_command(device_t self, uint8_t command); static void omap2_nand_address(device_t self, uint8_t address); static void omap2_nand_busy(device_t self); static void omap2_nand_read_1(device_t self, uint8_t *data); static void omap2_nand_write_1(device_t self, uint8_t data); static void omap2_nand_read_2(device_t self, uint16_t *data); static void omap2_nand_write_2(device_t self, uint16_t data); bool omap2_nand_isbusy(device_t self); static void omap2_nand_read_buf_1(device_t self, void *buf, size_t len); static void omap2_nand_read_buf_2(device_t self, void *buf, size_t len); static void omap2_nand_write_buf_1(device_t self, const void *buf, size_t len); static void omap2_nand_write_buf_2(device_t self, const void *buf, size_t len); #ifdef OMAP2_NAND_HARDWARE_ECC static int omap2_nand_ecc_init(device_t self); static int omap2_nand_ecc_prepare(device_t self, int mode); static int omap2_nand_ecc_compute(device_t self, const uint8_t *data, uint8_t *ecc); static int omap2_nand_ecc_correct(device_t self, uint8_t *data, const uint8_t *oldecc, const uint8_t *calcecc); #endif struct omap2_nand_softc { device_t sc_dev; device_t sc_nanddev; int sc_cs; int sc_buswidth; /* 0: 8bit, 1: 16bit */ struct nand_interface sc_nand_if; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; bus_space_handle_t sc_gpmc_ioh; bus_size_t sc_cmd_reg; bus_size_t sc_addr_reg; bus_size_t sc_data_reg; }; static const char * compatible[] = { "ti,omap2-nand", "ti,omap2-onenand", NULL }; CFATTACH_DECL_NEW(omapnand, sizeof(struct omap2_nand_softc), omap2_nand_match, omap2_nand_attach, NULL, NULL); static inline uint32_t gpmc_register_read(struct omap2_nand_softc *sc, bus_size_t reg) { return bus_space_read_4(sc->sc_iot, sc->sc_gpmc_ioh, reg); } static inline void gpmc_register_write(struct omap2_nand_softc *sc, bus_size_t reg, const uint32_t data) { bus_space_write_4(sc->sc_iot, sc->sc_gpmc_ioh, reg, data); } static void omap2_nand_command(device_t self, uint8_t command) { struct omap2_nand_softc *sc = device_private(self); bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->sc_cmd_reg, command); }; static void omap2_nand_address(device_t self, uint8_t address) { struct omap2_nand_softc *sc = device_private(self); bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->sc_addr_reg, address); }; bool omap2_nand_isbusy(device_t self) { struct omap2_nand_softc *sc = device_private(self); uint8_t status; DELAY(1); /* just to be sure we are not early */ bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->sc_cmd_reg, ONFI_READ_STATUS); DELAY(1); status = bus_space_read_1(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg); return !(status & ONFI_STATUS_RDY); }; static int omap2_nand_match(device_t parent, cfdata_t match, void *aux) { struct fdt_attach_args * const faa = aux; return of_match_compatible(faa->faa_phandle, compatible); } static void omap2_nand_attach(device_t parent, device_t self, void *aux) { struct omap2_nand_softc *sc = device_private(self); struct fdt_attach_args * const faa = aux; const int phandle = faa->faa_phandle; struct flash_attach_args flash; bus_addr_t addr, part_addr; bus_size_t size, part_size; const u_int *prop; uint32_t val; int len, child; if (fdtbus_get_reg(OF_parent(phandle), 0, &addr, &size) != 0) { aprint_error(": couldn't get registers\n"); return; } sc->sc_iot = faa->faa_bst; sc->sc_dev = self; prop = fdtbus_get_prop(phandle, "reg", &len); if (prop == NULL || len < 4) { aprint_error(": couldn't read reg property\n"); return; } sc->sc_cs = be32toh(prop[0]); /* map i/o space */ if (bus_space_map(sc->sc_iot, addr, size, 0, &sc->sc_gpmc_ioh) != 0) { aprint_error(": couldn't map registers\n"); return; } if (bus_space_subregion(sc->sc_iot, sc->sc_gpmc_ioh, GPMC_CS_CONFIG(sc->sc_cs), 0x30, &sc->sc_ioh) != 0) { aprint_error(": couldn't map cs registers\n"); return; } aprint_naive("\n"); aprint_normal(": CS%d\n", sc->sc_cs); sc->sc_cmd_reg = GPMC_NAND_COMMAND_0 - GPMC_CONFIG1_0; sc->sc_addr_reg = GPMC_NAND_ADDRESS_0 - GPMC_CONFIG1_0; sc->sc_data_reg = GPMC_NAND_DATA_0 - GPMC_CONFIG1_0; /* turn off write protection if enabled */ val = gpmc_register_read(sc, GPMC_CONFIG); val |= NAND_WP_BIT; gpmc_register_write(sc, GPMC_CONFIG, val); /* * do the reset dance for NAND */ bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->sc_cmd_reg, ONFI_RESET); omap2_nand_busy(self); /* read GPMC_CONFIG1_i to get buswidth */ val = bus_space_read_4(sc->sc_iot, sc->sc_ioh, GPMC_CONFIG1_i); if ((val & DEVICESIZE) == MASKEDINT(DEVICESIZE, 0x01)) { /* 16bit */ sc->sc_buswidth = 1; } else if ((val & DEVICESIZE) == MASKEDINT(DEVICESIZE, 0x00)) { /* 8bit */ sc->sc_buswidth = 0; } else { panic("invalid buswidth reported by config1"); } nand_init_interface(&sc->sc_nand_if); sc->sc_nand_if.command = &omap2_nand_command; sc->sc_nand_if.address = &omap2_nand_address; sc->sc_nand_if.read_buf_1 = &omap2_nand_read_buf_1; sc->sc_nand_if.read_buf_2 = &omap2_nand_read_buf_2; sc->sc_nand_if.read_1 = &omap2_nand_read_1; sc->sc_nand_if.read_2 = &omap2_nand_read_2; sc->sc_nand_if.write_buf_1 = &omap2_nand_write_buf_1; sc->sc_nand_if.write_buf_2 = &omap2_nand_write_buf_2; sc->sc_nand_if.write_1 = &omap2_nand_write_1; sc->sc_nand_if.write_2 = &omap2_nand_write_2; sc->sc_nand_if.busy = &omap2_nand_busy; #ifdef OMAP2_NAND_HARDWARE_ECC omap2_nand_ecc_init(self); sc->sc_nand_if.ecc_compute = &omap2_nand_ecc_compute; sc->sc_nand_if.ecc_correct = &omap2_nand_ecc_correct; sc->sc_nand_if.ecc_prepare = &omap2_nand_ecc_prepare; sc->sc_nand_if.ecc.necc_code_size = 3; sc->sc_nand_if.ecc.necc_block_size = 512; sc->sc_nand_if.ecc.necc_type = NAND_ECC_TYPE_HW; #else sc->sc_nand_if.ecc.necc_code_size = 3; sc->sc_nand_if.ecc.necc_block_size = 256; #endif /* OMAP2_NAND_HARDWARE_ECC */ if (!pmf_device_register1(sc->sc_dev, NULL, NULL, NULL)) aprint_error_dev(sc->sc_dev, "couldn't establish power handler\n"); sc->sc_nanddev = nand_attach_mi(&sc->sc_nand_if, sc->sc_dev); if (sc->sc_nanddev == NULL) return; for (child = OF_child(phandle); child; child = OF_peer(child)) { if (!fdtbus_status_okay(child)) continue; if (fdtbus_get_reg(child, 0, &part_addr, &part_size) != 0) { aprint_error_dev(self, "couldn't parse partition %s\n", fdtbus_get_string(child, "name")); continue; } memset(&flash, 0, sizeof(flash)); flash.flash_if = &nand_flash_if; flash.partinfo.part_offset = part_addr; flash.partinfo.part_size = part_size; flash.partinfo.part_flags = 0; flash.partinfo.part_name = fdtbus_get_string(child, "label"); if (flash.partinfo.part_name == NULL) flash.partinfo.part_name = fdtbus_get_string(child, "name"); config_found_ia(sc->sc_nanddev, "flashbus", &flash, flash_print); } } static void omap2_nand_busy(device_t self) { struct omap2_nand_softc *sc = device_private(self); while (!(gpmc_register_read(sc, GPMC_STATUS) & WAIT0)) { DELAY(1); } } static void omap2_nand_read_1(device_t self, uint8_t *data) { struct omap2_nand_softc *sc = device_private(self); *data = bus_space_read_1(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg); } static void omap2_nand_write_1(device_t self, uint8_t data) { struct omap2_nand_softc *sc = device_private(self); bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg, data); } static void omap2_nand_read_2(device_t self, uint16_t *data) { struct omap2_nand_softc *sc = device_private(self); *data = bus_space_read_2(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg); } static void omap2_nand_write_2(device_t self, uint16_t data) { struct omap2_nand_softc *sc = device_private(self); bus_space_write_2(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg, data); } static void omap2_nand_read_buf_1(device_t self, void *buf, size_t len) { struct omap2_nand_softc *sc = device_private(self); KASSERT(buf != NULL); KASSERT(len >= 1); bus_space_read_multi_1(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg, buf, len); } static void omap2_nand_read_buf_2(device_t self, void *buf, size_t len) { struct omap2_nand_softc *sc = device_private(self); KASSERT(buf != NULL); KASSERT(len >= 2); KASSERT(!(len & 0x01)); bus_space_read_multi_2(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg, buf, len / 2); } static void omap2_nand_write_buf_1(device_t self, const void *buf, size_t len) { struct omap2_nand_softc *sc = device_private(self); KASSERT(buf != NULL); KASSERT(len >= 1); bus_space_write_multi_1(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg, buf, len); } static void omap2_nand_write_buf_2(device_t self, const void *buf, size_t len) { struct omap2_nand_softc *sc = device_private(self); KASSERT(buf != NULL); KASSERT(len >= 2); KASSERT(!(len & 0x01)); bus_space_write_multi_2(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg, buf, len / 2); } #ifdef OMAP2_NAND_HARDWARE_ECC static uint32_t convert_ecc(const uint8_t *ecc) { return ecc[0] | (ecc[1] << 16) | ((ecc[2] & 0xf0) << 20) | ((ecc[2] & 0x0f) << 8); } static int omap2_nand_ecc_init(device_t self) { struct omap2_nand_softc *sc = device_private(self); uint32_t val; val = gpmc_register_read(sc, GPMC_ECC_CONTROL); /* clear ecc, select ecc register 1 */ val &= ~ECCPOINTER; val |= ECCCLEAR | MASKEDINT(ECCPOINTER, 1); gpmc_register_write(sc, GPMC_ECC_CONTROL, val); /* XXX too many MAGIC */ /* set ecc size to 512, set all regs to eccsize1*/ val = gpmc_register_read(sc, GPMC_ECC_SIZE_CONFIG); val &= ~ECCSIZE1; val |= MASKEDINT(ECCSIZE1, 512) | 0x0f; gpmc_register_write(sc, GPMC_ECC_CONTROL, val); return 0; } static int omap2_nand_ecc_compute(device_t self, const uint8_t *data, uint8_t *ecc) { struct omap2_nand_softc *sc = device_private(self); uint32_t val; /* read ecc result register */ val = gpmc_register_read(sc, GPMC_ECC1_RESULT); ecc[0] = val & 0xff; ecc[1] = (val >> 16) & 0xff; ecc[2] = ((val >> 8) & 0x0f) | ((val >> 20) & 0xf0); /* disable ecc engine */ val = gpmc_register_read(sc, GPMC_ECC_CONFIG); val &= ~ECCENABLE; gpmc_register_write(sc, GPMC_ECC_CONFIG, val); return 0; } static int omap2_nand_ecc_prepare(device_t self, int mode) { struct omap2_nand_softc *sc = device_private(self); uint32_t val; /* same for read/write */ switch (mode) { case NAND_ECC_READ: case NAND_ECC_WRITE: val = gpmc_register_read(sc, GPMC_ECC_CONTROL); /* clear ecc, select ecc register 1 */ val &= ~ECCPOINTER; val |= ECCCLEAR | MASKEDINT(ECCPOINTER, 1); gpmc_register_write(sc, GPMC_ECC_CONTROL, val); val = gpmc_register_read(sc, GPMC_ECC_CONFIG); val &= ~ECCCS; val |= ECCENABLE | MASKEDINT(ECCCS, sc->sc_cs); if (sc->sc_buswidth == 1) val |= ECC16B; else val &= ~ECC16B; gpmc_register_write(sc, GPMC_ECC_CONFIG, val); break; default: aprint_error_dev(self, "invalid i/o mode for ecc prepare\n"); return -1; } return 0; } static int omap2_nand_ecc_correct(device_t self, uint8_t *data, const uint8_t *oldecc, const uint8_t *calcecc) { uint32_t oecc, cecc, xor; uint16_t parity, offset; uint8_t bit; oecc = convert_ecc(oldecc); cecc = convert_ecc(calcecc); /* get the difference */ xor = oecc ^ cecc; /* the data was correct if all bits are zero */ if (xor == 0x00) return NAND_ECC_OK; switch (popcount32(xor)) { case 12: /* single byte error */ parity = xor >> 16; bit = (parity & 0x07); offset = (parity >> 3) & 0x01ff; /* correct bit */ data[offset] ^= (0x01 << bit); return NAND_ECC_CORRECTED; case 1: return NAND_ECC_INVALID; default: /* erased page! */ if ((oecc == 0x0fff0fff) && (cecc == 0x00000000)) return NAND_ECC_OK; return NAND_ECC_TWOBIT; } } #endif /* !OMAP2_NAND_HARDWARE_ECC */