Adds an 8-port GPIO expansion chip that connects over I2C and a simple I2C bus api.
Cheers, Andrew
From 0babece5fd378a12a71c43791c76308751c6dc96 Mon Sep 17 00:00:00 2001 From: Andrzej Zaborowski <[EMAIL PROTECTED]> Date: Fri, 16 Mar 2007 15:11:23 +0100 Subject: [PATCH] Maxim MAX7310 GPIO expander. I2C bus support. --- hw/i2c.h | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/max7310.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vl.h | 4 + 3 files changed, 340 insertions(+), 0 deletions(-) diff --git a/hw/i2c.h b/hw/i2c.h new file mode 100644 index 0000000..d866f28 --- /dev/null +++ b/hw/i2c.h @@ -0,0 +1,160 @@ +/* + * Simplified I2C(tm) bus / SMBus(tm). + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski <[EMAIL PROTECTED]> + * + * This file is licensed under GNU GPL v2. + */ + +#define I2C_MAX_MSG 4096 + +struct i2c_slave_s { + uint8_t address; + int (*tx)(void *opaque, uint8_t *data, int len); + void (*start)(void *opaque, int dir); + void (*stop)(void *opaque); + void *opaque; +}; + +struct i2c_bus_s { + struct i2c_slave_s *slave[0x80]; + uint8_t current; + int dir; +}; + +/* I2C master - drives the clock signal on a bus. There can be multiple + * masters on one bus. */ +struct i2c_master_s { + struct i2c_bus_s *bus; + uint8_t message[I2C_MAX_MSG]; + int message_len; + int ack; + + uint8_t data; +}; + +static inline int i2c_bus_start(struct i2c_bus_s *bus, uint8_t byte) +{ + struct i2c_slave_s *slave; + + bus->current = byte >> 1; + bus->dir = byte & 1; + slave = bus->slave[bus->current]; + + return !slave; +} + +static inline int i2c_start_submit(struct i2c_bus_s *bus) +{ + struct i2c_slave_s *slave = bus->slave[bus->current]; + if (!slave) + return 1; + + if (slave->start) + slave->start(slave->opaque, bus->dir); + return 0; +} + +static inline int i2c_stop_submit(struct i2c_bus_s *bus) +{ + struct i2c_slave_s *slave = bus->slave[bus->current]; + if (!slave) + return 1; + + if (slave->stop) + slave->stop(slave->opaque); + return 0; +} + +static inline int i2c_msg_submit(struct i2c_bus_s *bus, + uint8_t message[], int len) +{ + struct i2c_slave_s *slave = bus->slave[bus->current]; + if (!slave) + return 1; + + return slave->tx ? slave->tx(slave->opaque, message, len) : 1; +} + +static inline void i2c_master_submit(struct i2c_master_s *master, + int start, int stop) +{ + int ret = 0; + + if (!master->bus) { + master->ack = 0; + return; + } + + if (start) { + if (master->message_len) + if (!master->bus->dir) { /* Master --> Slave */ + i2c_start_submit(master->bus); + ret = i2c_msg_submit(master->bus, + master->message, master->message_len); + master->message_len = 0; + } + + ret = i2c_bus_start(master->bus, master->data); + master->message_len = 0; + + if (master->bus->dir) { /* Master <-- Slave */ + i2c_start_submit(master->bus); + master->message_len = 1; + if (stop) + i2c_msg_submit(master->bus, master->message, 0); + } + } else if (stop < 2) { + if (!master->bus->dir) { /* Master --> Slave */ + if (master->message_len < I2C_MAX_MSG) + master->message[master->message_len ++] = master->data; + } else { /* Master <-- Slave */ + ret = i2c_msg_submit(master->bus, + master->message, master->message_len); + master->data = master->message[0]; + } + } + + if (stop) { + if (!master->bus->dir) { /* Master --> Slave */ + i2c_start_submit(master->bus); + ret = i2c_msg_submit(master->bus, + master->message, master->message_len); + master->message_len = 0; + } + + i2c_stop_submit(master->bus); + } + + master->ack = !ret; +} + +/* Call with zero `addr' to detach. */ +static inline void i2c_slave_attach(struct i2c_bus_s *bus, uint8_t addr, + struct i2c_slave_s *dev) +{ + if (addr >= 0x80) + cpu_abort(cpu_single_env, "bad I2C address"); + + if (dev->address) + bus->slave[dev->address] = 0; + + dev->address = addr; + + if (dev->address) + bus->slave[dev->address] = dev; +} + +static inline void i2c_master_attach(struct i2c_bus_s *bus, + struct i2c_master_s *dev) +{ + dev->bus = bus; +} + +/* max7310.c */ +struct i2c_slave_s *max7310_init(void); +void max7310_reset(struct i2c_slave_s *i2c); +void max7310_gpio_set(struct i2c_slave_s *i2c, int line, int level); +void max7310_gpio_handler_set(struct i2c_slave_s *i2c, int line, + gpio_handler_t handler, void *opaque); diff --git a/hw/max7310.c b/hw/max7310.c new file mode 100644 index 0000000..7e8813d --- /dev/null +++ b/hw/max7310.c @@ -0,0 +1,176 @@ +/* + * MAX7310 8-port GPIO expansion chip. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski <[EMAIL PROTECTED]> + * + * This file is licensed under GNU GPL v2. + */ + +#include "vl.h" + +struct max7310_s { + uint8_t level; + uint8_t direction; + uint8_t polarity; + uint8_t status; + uint8_t command; + int i2c_dir; + struct i2c_slave_s i2c; + struct { + gpio_handler_t fn; + void *opaque; + } handler[8]; +}; + +void max7310_reset(struct i2c_slave_s *i2c) +{ + struct max7310_s *s = (struct max7310_s *) i2c->opaque; + s->level &= s->direction; + s->direction = 0xff; + s->polarity = 0xf0; + s->status = 0x01; + s->command = 0x00; +} + +static void max7310_start(void *opaque, int dir) +{ + struct max7310_s *s = (struct max7310_s *) opaque; + s->i2c_dir = dir; +} + +static int max7310_read(void *opaque, uint8_t *data, int len) +{ + struct max7310_s *s = (struct max7310_s *) opaque; + + switch (s->command) { + case 0x00: /* Input port */ + memset(data, s->level ^ s->polarity, len); + break; + + case 0x01: /* Output port */ + memset(data, s->level & ~s->direction, len); + break; + + case 0x02: /* Polarity inversion */ + memset(data, s->polarity, len); + break; + + case 0x03: /* Configuration */ + memset(data, s->direction, len); + break; + + case 0x04: /* Timeout */ + memset(data, s->status, len); + break; + + case 0xff: /* Reserved */ + memset(data, 0xff, len); + break; + + default: +#ifdef VERBOSE + printf("%s: unknown register %02x\n", __FUNCTION__, s->command); +#endif + return 1; + } + return 0; +} + +static int max7310_write(void *opaque, uint8_t *data, int len) +{ + struct max7310_s *s = (struct max7310_s *) opaque; + uint8_t diff; + int line; + + if (len >= 1) + s->command = data[0]; + + if (len < 2) { +#ifdef VERBOSE + printf("%s: message too short (%i bytes)\n", __FUNCTION__, len); +#endif + return 0; + } + + switch (s->command) { + case 0x01: /* Output port */ + for (diff = (data[1] ^ s->level) & ~s->direction; diff; + diff &= ~(1 << line)) { + line = ffs(diff) - 1; + if (s->handler[line].fn) + s->handler[line].fn(line, (data[1] >> line) & 1, + s->handler[line].opaque); + } + s->level = (s->level & s->direction) | (data[1] & ~s->direction); + break; + + case 0x02: /* Polarity inversion */ + s->polarity = data[1]; + break; + + case 0x03: /* Configuration */ + s->level &= ~(s->direction ^ data[1]); + s->direction = data[1]; + break; + + case 0x04: /* Timeout */ + s->status = data[1]; + break; + + default: +#ifdef VERBOSE + printf("%s: unknown register %02x\n", __FUNCTION__, s->command); +#endif + return 1; + } + + return 0; +} + +static int max7310_tx(void *opaque, uint8_t *data, int len) +{ + struct max7310_s *s = (struct max7310_s *) opaque; + if (len) { + if (s->i2c_dir) + return max7310_write(opaque, data, len); + else + return max7310_read(opaque, data, len); + } + + return 0; +} + +struct i2c_slave_s *max7310_init(void) +{ + struct max7310_s *s = qemu_mallocz(sizeof(struct max7310_s)); + s->i2c.opaque = s; + s->i2c.tx = max7310_tx; + s->i2c.start = max7310_start; + + max7310_reset(&s->i2c); + return &s->i2c; +} + +void max7310_gpio_set(struct i2c_slave_s *i2c, int line, int level) +{ + struct max7310_s *s = (struct max7310_s *) i2c->opaque; + if (line >= sizeof(s->handler) / sizeof(*s->handler) || line < 0) + cpu_abort(cpu_single_env, "bad GPIO line"); + + if (level) + s->level |= s->direction & (1 << line); + else + s->level &= ~(s->direction & (1 << line)); +} + +void max7310_gpio_handler_set(struct i2c_slave_s *i2c, int line, + gpio_handler_t handler, void *opaque) +{ + struct max7310_s *s = (struct max7310_s *) i2c->opaque; + if (line >= sizeof(s->handler) / sizeof(*s->handler) || line < 0) + cpu_abort(cpu_single_env, "bad GPIO line"); + + s->handler[line].fn = handler; + s->handler[line].opaque = opaque; +} diff --git a/vl.h b/vl.h index 95f3139..b30fb87 100644 --- a/vl.h +++ b/vl.h @@ -1438,6 +1438,10 @@ struct pcmcia_card_s { /* dscm1xxxx.c */ struct pcmcia_card_s *dscm1xxxx_init(BlockDriverState *bdrv); +typedef void (*gpio_handler_t)(int line, int level, void *opaque); + +#include "hw/i2c.h" + #include "gdbstub.h" #endif /* defined(QEMU_TOOL) */ -- 1.4.4.3
_______________________________________________ Qemu-devel mailing list Qemu-devel@nongnu.org http://lists.nongnu.org/mailman/listinfo/qemu-devel