diff mbox

[v3,2/2] Add AT24Cxx I2C EEPROM device model

Message ID 8eaef7f8857965619bee46e9fef387738f44bf19.1366967944.git.jan.kiszka@siemens.com
State New
Headers show

Commit Message

Jan Kiszka April 26, 2013, 9:19 a.m. UTC
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.kiszka@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

Comments

Peter Crosthwaite April 26, 2013, 12:16 p.m. UTC | #1
Hi Jan,

On Fri, Apr 26, 2013 at 7:19 PM, Jan Kiszka <jan.kiszka@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.kiszka@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

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.

Regards,
Peter

> +    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_UINT32("size", AT24State, size, 0),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void at24_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";
> +    dc->reset = at24_reset;
> +    dc->realize = at24_realize;
> +    dc->vmsd = &vmstate_at24;
> +    dc->props = at24_properties;
> +}
> +
> +static const TypeInfo at24_info = {
> +    .name          = "at24",
> +    .parent        = TYPE_I2C_SLAVE,
> +    .instance_size = sizeof(AT24State),
> +    .class_init    = at24_class_init,
> +};
> +
> +static void at24_register_types(void)
> +{
> +    /* buffering is based on this, enforce it early */
> +    assert(AT24_MAX_PAGE_LEN <= BDRV_SECTOR_SIZE);
> +
> +    type_register_static(&at24_info);
> +}
> +
> +type_init(at24_register_types)
> --
> 1.7.3.4
>
>
Jan Kiszka April 26, 2013, 12:28 p.m. UTC | #2
On 2013-04-26 14:16, Peter Crosthwaite wrote:
> Hi Jan,
> 
> On Fri, Apr 26, 2013 at 7:19 PM, Jan Kiszka <jan.kiszka@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.kiszka@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
diff mbox

Patch

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 },
+    { },
+};
+
+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;
+    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_UINT32("size", AT24State, size, 0),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void at24_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";
+    dc->reset = at24_reset;
+    dc->realize = at24_realize;
+    dc->vmsd = &vmstate_at24;
+    dc->props = at24_properties;
+}
+
+static const TypeInfo at24_info = {
+    .name          = "at24",
+    .parent        = TYPE_I2C_SLAVE,
+    .instance_size = sizeof(AT24State),
+    .class_init    = at24_class_init,
+};
+
+static void at24_register_types(void)
+{
+    /* buffering is based on this, enforce it early */
+    assert(AT24_MAX_PAGE_LEN <= BDRV_SECTOR_SIZE);
+
+    type_register_static(&at24_info);
+}
+
+type_init(at24_register_types)