From patchwork Mon Apr 29 13:09:18 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jan Kiszka X-Patchwork-Id: 240367 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 13A5C2C00C4 for ; Mon, 29 Apr 2013 23:10:02 +1000 (EST) Received: from localhost ([::1]:50609 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UWnqC-0000kz-5G for incoming@patchwork.ozlabs.org; Mon, 29 Apr 2013 09:10:00 -0400 Received: from eggs.gnu.org ([208.118.235.92]:46423) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UWnpd-0000iq-NP for qemu-devel@nongnu.org; Mon, 29 Apr 2013 09:09:30 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1UWnpZ-0000Sw-H0 for qemu-devel@nongnu.org; Mon, 29 Apr 2013 09:09:25 -0400 Received: from david.siemens.de ([192.35.17.14]:23887) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UWnpZ-0000Rv-3L for qemu-devel@nongnu.org; Mon, 29 Apr 2013 09:09:21 -0400 Received: from mail1.siemens.de (localhost [127.0.0.1]) by david.siemens.de (8.13.6/8.13.6) with ESMTP id r3TD9JFm019780; Mon, 29 Apr 2013 15:09:19 +0200 Received: from mchn199C.mchp.siemens.de ([139.25.109.49]) by mail1.siemens.de (8.13.6/8.13.6) with ESMTP id r3TD9IXv017881; Mon, 29 Apr 2013 15:09:19 +0200 From: Jan Kiszka To: qemu-devel , Anthony Liguori Date: Mon, 29 Apr 2013 15:09:18 +0200 Message-Id: X-Mailer: git-send-email 1.7.3.4 In-Reply-To: References: In-Reply-To: References: X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x X-Received-From: 192.35.17.14 Cc: Peter Crosthwaite , Andreas Faerber Subject: [Qemu-devel] [PATCH v4 2/2] Add AT24Cxx I2C EEPROM device model X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org This implements I2C EEPROMs of the AT24Cxx series. Sizes from 1Kbit to 1024Kbit are supported. Each EEPROM is backed by a block device. Its size can be explicitly specified by selecting the exact device type (required for sizes < 512, the blockdev sector size) or implicitly by providing a block device image of the corresponding size. Device addresses are built from the device number property. Write protection can be configured by declaring the block device read-only. Signed-off-by: Jan Kiszka --- hw/nvram/Makefile.objs | 2 +- hw/nvram/at24.c | 391 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 392 insertions(+), 1 deletions(-) create mode 100644 hw/nvram/at24.c diff --git a/hw/nvram/Makefile.objs b/hw/nvram/Makefile.objs index e9a6694..8271cd6 100644 --- a/hw/nvram/Makefile.objs +++ b/hw/nvram/Makefile.objs @@ -1,5 +1,5 @@ common-obj-$(CONFIG_DS1225Y) += ds1225y.o -common-obj-y += eeprom93xx.o +common-obj-y += eeprom93xx.o at24.o common-obj-y += fw_cfg.o common-obj-$(CONFIG_MAC_NVRAM) += mac_nvram.o obj-$(CONFIG_PSERIES) += spapr_nvram.o diff --git a/hw/nvram/at24.c b/hw/nvram/at24.c new file mode 100644 index 0000000..654a4e2 --- /dev/null +++ b/hw/nvram/at24.c @@ -0,0 +1,391 @@ +/* + * AT24Cxx EEPROM emulation + * + * Copyright (c) Siemens AG, 2012, 2013 + * Author: Jan Kiszka + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/hw.h" +#include "hw/i2c/i2c.h" +#include "hw/block/block.h" +#include "sysemu/blockdev.h" + +#define TYPE_AT24 "at24" +#define AT24(obj) OBJECT_CHECK(AT24State, (obj), TYPE_AT24) + +#define AT24_BASE_ADDRESS 0x50 +#define AT24_MAX_PAGE_LEN 256 + +typedef struct AT24Parameters { + const char *desc; + unsigned int size; + unsigned int page_size; + unsigned int device_bits; + unsigned int hi_addr_bits; + bool addr16; +} AT24Parameters; + +typedef enum AT24TransferState { + AT24_IDLE, + AT24_RD_ADDR, + AT24_WR_ADDR_HI, + AT24_WR_ADDR_LO, + AT24_RW_DATA0, + AT24_RD_DATA, + AT24_WR_DATA, +} AT24TransferState; + +typedef struct AT24State { + I2CSlave parent_obj; + + BlockConf block_conf; + BlockDriverState *bs; + bool wp; + unsigned int addr_mask; + unsigned int page_mask; + bool addr16; + unsigned int hi_addr_mask; + uint8_t device; + AT24TransferState transfer_state; + uint8_t sector_buffer[BDRV_SECTOR_SIZE]; + int cached_sector; + bool cache_dirty; + uint32_t transfer_addr; +} AT24State; + +static void at24_flush_transfer_buffer(AT24State *s) +{ + if (s->cached_sector < 0 || !s->cache_dirty) { + return; + } + bdrv_write(s->bs, s->cached_sector, s->sector_buffer, 1); + s->cache_dirty = false; +} + +static void at24_event(I2CSlave *i2c, enum i2c_event event, uint8_t param) +{ + AT24State *s = AT24(i2c); + + switch (event) { + case I2C_START_SEND: + switch (s->transfer_state) { + case AT24_IDLE: + if (s->addr16) { + s->transfer_addr = (param & s->hi_addr_mask) << 16; + s->transfer_state = AT24_WR_ADDR_HI; + } else { + s->transfer_addr = (param & s->hi_addr_mask) << 8; + s->transfer_state = AT24_WR_ADDR_LO; + } + break; + default: + s->transfer_state = AT24_IDLE; + break; + } + break; + case I2C_START_RECV: + switch (s->transfer_state) { + case AT24_IDLE: + s->transfer_state = AT24_RD_ADDR; + break; + case AT24_RW_DATA0: + s->transfer_state = AT24_RD_DATA; + break; + default: + s->transfer_state = AT24_IDLE; + break; + } + break; + case I2C_FINISH: + switch (s->transfer_state) { + case AT24_WR_DATA: + at24_flush_transfer_buffer(s); + /* fall through */ + default: + s->transfer_state = AT24_IDLE; + break; + } + break; + default: + s->transfer_state = AT24_IDLE; + break; + } +} + +static int at24_cache_sector(AT24State *s, int sector) +{ + int ret; + + if (s->cached_sector == sector) { + return 0; + } + ret = bdrv_read(s->bs, sector, s->sector_buffer, 1); + if (ret < 0) { + s->cached_sector = -1; + return ret; + } + s->cached_sector = sector; + s->cache_dirty = false; + return 0; +} + +static int at24_tx(I2CSlave *i2c, uint8_t data) +{ + AT24State *s = AT24(i2c); + + switch (s->transfer_state) { + case AT24_WR_ADDR_HI: + s->transfer_addr |= (data << 8) & s->addr_mask; + s->transfer_state = AT24_WR_ADDR_LO; + break; + case AT24_WR_ADDR_LO: + s->transfer_addr |= data & s->addr_mask; + s->transfer_state = AT24_RW_DATA0; + break; + case AT24_RW_DATA0: + s->transfer_state = AT24_WR_DATA; + if (at24_cache_sector(s, s->transfer_addr >> BDRV_SECTOR_BITS) < 0) { + s->transfer_state = AT24_IDLE; + return -1; + } + /* fall through */ + case AT24_WR_DATA: + if (!s->wp) { + s->sector_buffer[s->transfer_addr & ~BDRV_SECTOR_MASK] = data; + s->cache_dirty = true; + } + s->transfer_addr = (s->transfer_addr & s->page_mask) | + ((s->transfer_addr + 1) & ~s->page_mask); + break; + default: + s->transfer_state = AT24_IDLE; + return -1; + } + return 0; +} + +static int at24_rx(I2CSlave *i2c) +{ + AT24State *s = AT24(i2c); + unsigned int sector, offset; + int result; + + switch (s->transfer_state) { + case AT24_RD_ADDR: + s->transfer_state = AT24_IDLE; + result = s->transfer_addr; + break; + case AT24_RD_DATA: + sector = s->transfer_addr >> BDRV_SECTOR_BITS; + offset = s->transfer_addr & ~BDRV_SECTOR_MASK; + s->transfer_addr = (s->transfer_addr + 1) & s->addr_mask; + if (at24_cache_sector(s, sector) < 0) { + result = 0; + break; + } + result = s->sector_buffer[offset]; + break; + default: + s->transfer_state = AT24_IDLE; + result = 0; + break; + } + return result; +} + +static void at24_reset(DeviceState *d) +{ + AT24State *s = AT24(d); + + s->transfer_state = AT24_IDLE; + s->cached_sector = -1; +} + +static const AT24Parameters at24_parameters[] = { + { "AT24C1 EEPROM", 1*1024, 8, 3, 0, false }, + { "AT24C2 EEPROM", 2*1024, 8, 3, 0, false }, + { "AT24C4 EEPROM", 4*1024, 16, 2, 1, false }, + { "AT24C8 EEPROM", 8*1024, 16, 1, 2, false }, + { "AT24C16 EEPROM", 16*1024, 16, 0, 3, false }, + { "AT24C32 EEPROM", 32*1024, 32, 3, 0, true }, + { "AT24C62 EEPROM", 64*1024, 32, 3, 0, true }, + { "AT24C128 EEPROM", 128*1024, 64, 2, 0, true }, + { "AT24C256 EEPROM", 256*1024, 64, 2, 0, true }, + { "AT24C512 EEPROM", 512*1024, 128, 2, 0, true }, + { "AT24C1024 EEPROM", 1024*1024, 256, 1, 1, true }, + { }, +}; + +static void at24_realize(DeviceState *d, Error **errp) +{ + DeviceClass *dc = DEVICE_GET_CLASS(d); + I2CSlave *i2c = I2C_SLAVE(d); + AT24State *s = AT24(d); + const AT24Parameters *params; + int64_t image_size; + uint32_t size; + int dev_no; + + s->bs = s->block_conf.bs; + if (!s->bs) { + error_setg(errp, "drive property not set"); + return; + } + + s->wp = bdrv_is_read_only(s->bs); + + image_size = bdrv_getlength(s->bs); + + for (params = at24_parameters; params->size != 0; params++) { + if (params->desc == dc->desc) { + break; + } + } + size = params->size / 8; + + if (size == 0) { + size = image_size; + + for (params = at24_parameters; params->size != 0; params++) { + if (size * 8 == params->size) { + break; + } + } + if (params->size == 0) { + error_setg(errp, "invalid EEPROM size, must be 2^(10..17)"); + return; + } + } else if (image_size < size) { + error_setg(errp, "image file smaller than EEPROM size"); + return; + } + + s->addr_mask = size - 1; + s->page_mask = ~(params->page_size - 1); + s->hi_addr_mask = (1 << params->hi_addr_bits) - 1; + s->addr16 = params->addr16; + + dev_no = (s->device & ((1 << params->device_bits) - 1)) << + params->hi_addr_bits; + i2c_set_slave_address(i2c, AT24_BASE_ADDRESS | dev_no); + i2c_set_slave_address_mask(i2c, 0x7f & ~s->hi_addr_mask); +} + +static void at24_pre_save(void *opaque) +{ + AT24State *s = opaque; + + at24_flush_transfer_buffer(s); +} + +static int at24_post_load(void *opaque, int version_id) +{ + AT24State *s = opaque; + + s->cached_sector = -1; + return 0; +} + +static const VMStateDescription vmstate_at24 = { + .name = "at24", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .pre_save = at24_pre_save, + .post_load = at24_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32(transfer_state, AT24State), + VMSTATE_UINT32(transfer_addr, AT24State), + VMSTATE_END_OF_LIST() + } +}; + +static Property at24_properties[] = { + DEFINE_BLOCK_PROPERTIES(AT24State, block_conf), + DEFINE_PROP_UINT8("device", AT24State, device, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void at24_base_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass); + + sc->event = at24_event; + sc->recv = at24_rx; + sc->send = at24_tx; + dc->desc = "AT24Cxx EEPROM (defined by image size)"; + dc->reset = at24_reset; + dc->realize = at24_realize; + dc->vmsd = &vmstate_at24; + dc->props = at24_properties; +} + +static void at24cxx_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + const AT24Parameters *params = data; + + dc->desc = params->desc; +} + +#define AT24CXX_TYPE_INFO(type_name, param_no) \ + { \ + .name = (type_name), \ + .parent = TYPE_AT24, \ + .instance_size = sizeof(AT24State), \ + .class_init = at24cxx_class_init, \ + .class_data = (void *)&at24_parameters[(param_no)], \ + } + +static const TypeInfo at24_info[] = { + { + .name = "at24", + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(AT24State), + .class_init = at24_base_class_init, + }, + AT24CXX_TYPE_INFO("at24c1", 0), + AT24CXX_TYPE_INFO("at24c2", 1), + AT24CXX_TYPE_INFO("at24c4", 2), + AT24CXX_TYPE_INFO("at24c8", 3), + AT24CXX_TYPE_INFO("at24c16", 4), + AT24CXX_TYPE_INFO("at24c32", 5), + AT24CXX_TYPE_INFO("at24c64", 6), + AT24CXX_TYPE_INFO("at24c128", 7), + AT24CXX_TYPE_INFO("at24c256", 8), + AT24CXX_TYPE_INFO("at24c512", 9), + AT24CXX_TYPE_INFO("at24c1024", 10), +}; + +static void at24_register_types(void) +{ + unsigned int n; + + /* buffering is based on this, enforce it early */ + assert(AT24_MAX_PAGE_LEN <= BDRV_SECTOR_SIZE); + + for (n = 0; n < ARRAY_SIZE(at24_info); n++) { + type_register_static(&at24_info[n]); + } +} + +type_init(at24_register_types)