On 2013-04-26 14:16, Peter Crosthwaite wrote: > Hi Jan, > > On Fri, Apr 26, 2013 at 7:19 PM, Jan Kiszka <jan.kis...@siemens.com> wrote: >> 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 the "size" property (required for >> sizes < 512, the blockdev sector size) or is derived from the size of >> the backing block device. 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 <jan.kis...@siemens.com> >> --- >> hw/nvram/Makefile.objs | 2 +- >> hw/nvram/at24.c | 347 >> ++++++++++++++++++++++++++++++++++++++++++++++++ >> 2 files changed, 348 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..91e58e0 >> --- /dev/null >> +++ b/hw/nvram/at24.c >> @@ -0,0 +1,347 @@ >> +/* >> + * 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 { >> + 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; >> + uint32_t size; >> + 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[] = { >> + { 1*1024, 8, 3, 0, false }, >> + { 2*1024, 8, 3, 0, false }, >> + { 4*1024, 16, 2, 1, false }, >> + { 8*1024, 16, 1, 2, false }, >> + { 16*1024, 16, 0, 3, false }, >> + { 32*1024, 32, 3, 0, true }, >> + { 64*1024, 32, 3, 0, true }, >> + { 128*1024, 64, 2, 0, true }, >> + { 256*1024, 64, 2, 0, true }, >> + { 512*1024, 128, 2, 0, true }, >> + { 1024*1024, 256, 1, 1, true }, >> + { }, > > You could potentially add a string field to this array for the part > name. "at24c08" etc. > Then in the register types function you can iterate through the array > and instantiate a > type for each different part. This would allow you to go > > qemu-system-foo -device at24c08 > > rather than > > qemu-system-foo -device at24cXX,size=4096
You only need the size property if your EEPROM image is larger than the target type. But I could add explicit types, too. > > etc. > > See block/m25p80.c for a similar situation. > >> +}; >> + >> +static void at24_realize(DeviceState *d, Error **errp) >> +{ >> + I2CSlave *i2c = I2C_SLAVE(d); >> + AT24State *s = AT24(d); >> + const AT24Parameters *params; >> + int64_t image_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); >> + if (s->size == 0) { >> + s->size = image_size; >> + } else if (image_size < s->size) { >> + error_setg(errp, "image file smaller than specified EEPROM size"); >> + return; >> + } >> + >> + for (params = at24_parameters; params->size != 0; params++) { >> + if (s->size * 8 == params->size) { >> + break; >> + } >> + } >> + if (params->size == 0) { >> + error_setg(errp, "invalid EEPROM size, must be 2^(10..17)"); >> + return; >> + } >> + s->addr_mask = s->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; > > I still think this device bits has a place in the I2C layer itself. > But i'm happy to send > cleanup patches myself after a merge. I need this for another I2C > device and rather > not copy paste. Perfect, thanks. Jan -- Siemens AG, Corporate Technology, CT RTC ITP SDP-DE Corporate Competence Center Embedded Linux