From patchwork Sun Aug 28 16:57:03 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter Maydell X-Patchwork-Id: 3738 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 12A6723FBB for ; Sun, 28 Aug 2011 16:57:18 +0000 (UTC) Received: from mail-fx0-f52.google.com (mail-fx0-f52.google.com [209.85.161.52]) by fiordland.canonical.com (Postfix) with ESMTP id 02D1DA1878B for ; Sun, 28 Aug 2011 16:57:17 +0000 (UTC) Received: by mail-fx0-f52.google.com with SMTP id 18so5671092fxd.11 for ; Sun, 28 Aug 2011 09:57:17 -0700 (PDT) Received: by 10.223.88.214 with SMTP id b22mr3737547fam.5.1314550637898; Sun, 28 Aug 2011 09:57:17 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.152.11.8 with SMTP id m8cs69784lab; Sun, 28 Aug 2011 09:57:17 -0700 (PDT) Received: by 10.204.7.85 with SMTP id c21mr1531804bkc.407.1314550636834; Sun, 28 Aug 2011 09:57:16 -0700 (PDT) Received: from mnementh.archaic.org.uk (mnementh.archaic.org.uk [81.2.115.146]) by mx.google.com with ESMTPS id z2si5815239bkd.146.2011.08.28.09.57.15 (version=TLSv1/SSLv3 cipher=OTHER); Sun, 28 Aug 2011 09:57:15 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of pm215@archaic.org.uk designates 81.2.115.146 as permitted sender) client-ip=81.2.115.146; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of pm215@archaic.org.uk designates 81.2.115.146 as permitted sender) smtp.mail=pm215@archaic.org.uk Received: from pm215 by mnementh.archaic.org.uk with local (Exim 4.72) (envelope-from ) id 1QxifU-00070G-RA; Sun, 28 Aug 2011 17:57:08 +0100 From: Peter Maydell To: qemu-devel@nongnu.org Cc: Andrzej Zaborowski , "Edgar E. Iglesias" , patches@linaro.org Subject: [PATCH v2 13/18] omap_gpmc: Support NAND devices Date: Sun, 28 Aug 2011 17:57:03 +0100 Message-Id: <1314550628-26869-15-git-send-email-peter.maydell@linaro.org> X-Mailer: git-send-email 1.7.2.5 In-Reply-To: <1314550628-26869-1-git-send-email-peter.maydell@linaro.org> References: <1314550628-26869-1-git-send-email-peter.maydell@linaro.org> Support accesses to NAND devices, both by mapping them into the GPMC address space, and via the NAND_COMMAND, NAND_ADDRESS and NAND_DATA GPMC registers. Signed-off-by: Peter Maydell --- hw/omap.h | 1 + hw/omap_gpmc.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 208 insertions(+), 12 deletions(-) diff --git a/hw/omap.h b/hw/omap.h index 8509c82..2018636 100644 --- a/hw/omap.h +++ b/hw/omap.h @@ -122,6 +122,7 @@ struct omap_gpmc_s *omap_gpmc_init(struct omap_mpu_state_s *mpu, target_phys_addr_t base, qemu_irq irq); void omap_gpmc_reset(struct omap_gpmc_s *s); void omap_gpmc_attach(struct omap_gpmc_s *s, int cs, MemoryRegion *iomem); +void omap_gpmc_attach_nand(struct omap_gpmc_s *s, int cs, DeviceState *nand); /* * Common IRQ numbers for level 1 interrupt handler diff --git a/hw/omap_gpmc.c b/hw/omap_gpmc.c index c569aa1..d2de72f 100644 --- a/hw/omap_gpmc.c +++ b/hw/omap_gpmc.c @@ -43,6 +43,8 @@ struct omap_gpmc_s { uint32_t config[7]; MemoryRegion *iomem; MemoryRegion container; + MemoryRegion nandiomem; + DeviceState *dev; } cs_file[8]; int ecc_cs; int ecc_ptr; @@ -50,11 +52,135 @@ struct omap_gpmc_s { ECCState ecc[9]; }; +#define OMAP_GPMC_8BIT 0 +#define OMAP_GPMC_16BIT 1 +#define OMAP_GPMC_NOR 0 +#define OMAP_GPMC_NAND 2 + +static int omap_gpmc_devtype(struct omap_gpmc_cs_file_s *f) +{ + return (f->config[0] >> 10) & 3; +} + +static int omap_gpmc_devsize(struct omap_gpmc_cs_file_s *f) +{ + /* devsize field is really 2 bits but we ignore the high + * bit to ensure consistent behaviour if the guest sets + * it (values 2 and 3 are reserved in the TRM) + */ + return (f->config[0] >> 12) & 1; +} + static void omap_gpmc_int_update(struct omap_gpmc_s *s) { qemu_set_irq(s->irq, s->irqen & s->irqst); } +/* Access functions for when a NAND-like device is mapped into memory: + * all addresses in the region behave like accesses to the relevant + * GPMC_NAND_DATA_i register (which is actually implemented to call these) + */ +static uint64_t omap_nand_read(void *opaque, target_phys_addr_t addr, + unsigned size) +{ + struct omap_gpmc_cs_file_s *f = (struct omap_gpmc_cs_file_s *)opaque; + uint64_t v; + nand_setpins(f->dev, 0, 0, 0, 1, 0); + switch (omap_gpmc_devsize(f)) { + case OMAP_GPMC_8BIT: + v = nand_getio(f->dev); + if (size == 1) { + return v; + } + v |= (nand_getio(f->dev) << 8); + if (size == 2) { + return v; + } + v |= (nand_getio(f->dev) << 16); + v |= (nand_getio(f->dev) << 24); + return v; + case OMAP_GPMC_16BIT: + v = nand_getio(f->dev); + if (size == 1) { + /* 8 bit read from 16 bit device : probably a guest bug */ + return v & 0xff; + } + if (size == 2) { + return v; + } + v |= (nand_getio(f->dev) << 16); + return v; + default: + abort(); + } +} + +static void omap_nand_setio(DeviceState *dev, uint64_t value, + int nandsize, int size) +{ + /* Write the specified value to the NAND device, respecting + * both size of the NAND device and size of the write access. + */ + switch (nandsize) { + case OMAP_GPMC_8BIT: + switch (size) { + case 1: + nand_setio(dev, value & 0xff); + break; + case 2: + nand_setio(dev, value & 0xff); + nand_setio(dev, (value >> 8) & 0xff); + break; + case 4: + default: + nand_setio(dev, value & 0xff); + nand_setio(dev, (value >> 8) & 0xff); + nand_setio(dev, (value >> 16) & 0xff); + nand_setio(dev, (value >> 24) & 0xff); + break; + } + case OMAP_GPMC_16BIT: + switch (size) { + case 1: + /* writing to a 16bit device with 8bit access is probably a guest + * bug; pass the value through anyway. + */ + case 2: + nand_setio(dev, value & 0xffff); + break; + case 4: + default: + nand_setio(dev, value & 0xffff); + nand_setio(dev, (value >> 16) & 0xffff); + break; + } + } +} + +static void omap_nand_write(void *opaque, target_phys_addr_t addr, + uint64_t value, unsigned size) +{ + struct omap_gpmc_cs_file_s *f = (struct omap_gpmc_cs_file_s *)opaque; + nand_setpins(f->dev, 0, 0, 0, 1, 0); + omap_nand_setio(f->dev, value, omap_gpmc_devsize(f), size); +} + +static const MemoryRegionOps omap_nand_ops = { + .read = omap_nand_read, + .write = omap_nand_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static MemoryRegion *omap_gpmc_cs_memregion(struct omap_gpmc_s *s, int cs) +{ + /* Return the MemoryRegion* to map/unmap for this chipselect */ + struct omap_gpmc_cs_file_s *f = &s->cs_file[cs]; + if (omap_gpmc_devtype(f) == OMAP_GPMC_NOR) { + return f->iomem; + } + return &f->nandiomem; +} + static void omap_gpmc_cs_map(struct omap_gpmc_s *s, int cs) { struct omap_gpmc_cs_file_s *f = &s->cs_file[cs]; @@ -62,7 +188,7 @@ static void omap_gpmc_cs_map(struct omap_gpmc_s *s, int cs) uint32_t base = f->config[6] & 0x3f; uint32_t size; - if (!f->iomem) { + if (!f->iomem && !f->dev) { return; } @@ -86,7 +212,8 @@ static void omap_gpmc_cs_map(struct omap_gpmc_s *s, int cs) * that the same memory becomes accessible at every size bytes * starting from base. */ memory_region_init(&f->container, "omap-gpmc-file", size); - memory_region_add_subregion(&f->container, 0, f->iomem); + memory_region_add_subregion(&f->container, 0, + omap_gpmc_cs_memregion(s, cs)); memory_region_add_subregion(get_system_memory(), base, &f->container); } @@ -98,12 +225,11 @@ static void omap_gpmc_cs_unmap(struct omap_gpmc_s *s, int cs) /* Do nothing unless CSVALID */ return; } - if (!f->iomem) { + if (!f->iomem && !f->dev) { return; } - memory_region_del_subregion(get_system_memory(), &f->container); - memory_region_del_subregion(&f->container, f->iomem); + memory_region_del_subregion(&f->container, omap_gpmc_cs_memregion(s, cs)); memory_region_destroy(&f->container); } @@ -151,6 +277,24 @@ void omap_gpmc_reset(struct omap_gpmc_s *s) ecc_reset(&s->ecc[i]); } +static int gpmc_wordaccess_only(target_phys_addr_t addr) +{ + /* Return true if the register offset is to a register that + * only permits word width accesses. + * Non-word accesses are only OK for GPMC_NAND_DATA/ADDRESS/COMMAND + * for any chipselect. + */ + if (addr >= 0x60 && addr <= 0x1d4) { + int cs = (addr - 0x60) / 0x30; + addr -= cs * 0x30; + if (addr >= 0x7c && addr < 0x88) { + /* GPMC_NAND_COMMAND, GPMC_NAND_ADDRESS, GPMC_NAND_DATA */ + return 0; + } + } + return 1; +} + static uint64_t omap_gpmc_read(void *opaque, target_phys_addr_t addr, unsigned size) { @@ -158,7 +302,7 @@ static uint64_t omap_gpmc_read(void *opaque, target_phys_addr_t addr, int cs; struct omap_gpmc_cs_file_s *f; - if (size != 4) { + if (size != 4 && gpmc_wordaccess_only(addr)) { return omap_badwidth_read32(opaque, addr); } @@ -210,7 +354,10 @@ static uint64_t omap_gpmc_read(void *opaque, target_phys_addr_t addr, return f->config[5]; case 0x78: /* GPMC_CONFIG7 */ return f->config[6]; - case 0x84: /* GPMC_NAND_DATA */ + case 0x84 ... 0x87: /* GPMC_NAND_DATA */ + if (omap_gpmc_devtype(f) == OMAP_GPMC_NAND) { + return omap_nand_read(f, 0, size); + } return 0; } break; @@ -260,7 +407,7 @@ static void omap_gpmc_write(void *opaque, target_phys_addr_t addr, int cs; struct omap_gpmc_cs_file_s *f; - if (size != 4) { + if (size != 4 && gpmc_wordaccess_only(addr)) { return omap_badwidth_write32(opaque, addr, value); } @@ -336,11 +483,23 @@ static void omap_gpmc_write(void *opaque, target_phys_addr_t addr, omap_gpmc_cs_map(s, cs); } break; - case 0x7c: /* GPMC_NAND_COMMAND */ - case 0x80: /* GPMC_NAND_ADDRESS */ - case 0x84: /* GPMC_NAND_DATA */ + case 0x7c ... 0x7f: /* GPMC_NAND_COMMAND */ + if (omap_gpmc_devtype(f) == OMAP_GPMC_NAND) { + nand_setpins(f->dev, 1, 0, 0, 1, 0); /* CLE */ + omap_nand_setio(f->dev, value, omap_gpmc_devsize(f), size); + } + break; + case 0x80 ... 0x83: /* GPMC_NAND_ADDRESS */ + if (omap_gpmc_devtype(f) == OMAP_GPMC_NAND) { + nand_setpins(f->dev, 0, 1, 0, 1, 0); /* ALE */ + omap_nand_setio(f->dev, value, omap_gpmc_devsize(f), size); + } + break; + case 0x84 ... 0x87: /* GPMC_NAND_DATA */ + if (omap_gpmc_devtype(f) == OMAP_GPMC_NAND) { + omap_nand_write(f, 0, value, size); + } break; - default: goto bad_reg; } @@ -403,6 +562,7 @@ static const MemoryRegionOps omap_gpmc_ops = { struct omap_gpmc_s *omap_gpmc_init(struct omap_mpu_state_s *mpu, target_phys_addr_t base, qemu_irq irq) { + int cs; struct omap_gpmc_s *s = (struct omap_gpmc_s *) g_malloc0(sizeof(struct omap_gpmc_s)); @@ -413,6 +573,19 @@ struct omap_gpmc_s *omap_gpmc_init(struct omap_mpu_state_s *mpu, s->revision = cpu_class_omap3(mpu) ? 0x50 : 0x20; omap_gpmc_reset(s); + /* We have to register a different IO memory handler for each + * chip select region in case a NAND device is mapped there. We + * make the region the worst-case size of 256MB and rely on the + * container memory region in cs_map to chop it down to the actual + * guest-requested size. + */ + for (cs = 0; cs < 8; cs++) { + memory_region_init_io(&s->cs_file[cs].nandiomem, + &omap_nand_ops, + &s->cs_file[cs], + "omap-nand", + 256 * 1024 * 1024); + } return s; } @@ -428,6 +601,28 @@ void omap_gpmc_attach(struct omap_gpmc_s *s, int cs, MemoryRegion *iomem) f = &s->cs_file[cs]; omap_gpmc_cs_unmap(s, cs); + f->config[0] &= ~(0xf << 10); f->iomem = iomem; omap_gpmc_cs_map(s, cs); } + +void omap_gpmc_attach_nand(struct omap_gpmc_s *s, int cs, DeviceState *nand) +{ + struct omap_gpmc_cs_file_s *f; + assert(nand); + + if (cs < 0 || cs >= 8) { + fprintf(stderr, "%s: bad chip-select %i\n", __func__, cs); + exit(-1); + } + f = &s->cs_file[cs]; + + omap_gpmc_cs_unmap(s, cs); + f->config[0] &= ~(0xf << 10); + f->config[0] |= (OMAP_GPMC_NAND << 10); + f->dev = nand; + if (nand_getbuswidth(f->dev) == 16) { + f->config[0] |= OMAP_GPMC_16BIT << 12; + } + omap_gpmc_cs_map(s, cs); +}