From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1Lx3KC-0007ca-6F for qemu-devel@nongnu.org; Thu, 23 Apr 2009 14:07:04 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1Lx3KA-0007c7-RK for qemu-devel@nongnu.org; Thu, 23 Apr 2009 14:07:03 -0400 Received: from [199.232.76.173] (port=59495 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Lx3KA-0007c1-LL for qemu-devel@nongnu.org; Thu, 23 Apr 2009 14:07:02 -0400 Received: from flounder.pepperfish.net ([87.237.62.181]:34299) by monty-python.gnu.org with esmtps (TLS-1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.60) (envelope-from ) id 1Lx3K9-0005Ar-TC for qemu-devel@nongnu.org; Thu, 23 Apr 2009 14:07:02 -0400 Received: from [10.112.102.2] (helo=jennifer.kylikki.org) by flounder.pepperfish.net with esmtps (Exim 4.69 #1 (Debian)) id 1Lx3K7-0004wz-RW for ; Thu, 23 Apr 2009 19:07:00 +0100 Received: from derik.kyllikki.org ([192.168.7.20] helo=derik) by jennifer.kylikki.org with esmtp (Exim 4.63) (envelope-from ) id 1Lx3K8-0006KY-95 for qemu-devel@nongnu.org; Thu, 23 Apr 2009 19:07:00 +0100 Received: from vince by derik with local (Exim 4.69) (envelope-from ) id 1Lx3K8-00048W-71 for qemu-devel@nongnu.org; Thu, 23 Apr 2009 19:07:00 +0100 Date: Thu, 23 Apr 2009 19:07:00 +0100 From: Vincent Sanders Subject: [Qemu-devel] [PATCH 10/16] S3C I2C peripheral Message-ID: <20090423180700.GM4629@derik> References: <20090423171503.GC4629@derik> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20090423171503.GC4629@derik> List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org S3C I2C peripheral Signed-off-by: Vincent Sanders --- s3c24xx_iic.c | 322 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) diff -urN qemusvnclean/hw/s3c24xx_iic.c qemusvnpatches/hw/s3c24xx_iic.c --- qemusvnclean/hw/s3c24xx_iic.c 1970-01-01 01:00:00.000000000 +0100 +++ qemusvnpatches/hw/s3c24xx_iic.c 2009-04-23 17:09:08.000000000 +0100 @@ -0,0 +1,322 @@ +/* hw/s3c24xx_iic.c + * + * Samsung S3C24XX emulation + * + * Copyright 2006, 2007, 2008 Daniel Silverstone, Ben Dooks + * and Vincent Sanders + * + * This file is under the terms of the GNU General Public + * License Version 2 + */ + +#include "hw.h" +#include "i2c.h" + +#include "s3c24xx.h" + +/* i2c controller registers */ +#define S3C_IICCON (0x00) +#define S3C_IICSTAT (0x04) +#define S3C_IICADD (0x08) +#define S3C_IICDS (0x0C) +#define S3C_IICLC (0x10) + +#define S3C_IICCON_ACKEN (1<<7) +#define S3C_IICCON_TXDIV_16 (0<<6) +#define S3C_IICCON_TXDIV_512 (1<<6) +#define S3C_IICCON_IRQEN (1<<5) +#define S3C_IICCON_IRQPEND (1<<4) +#define S3C_IICCON_SCALE(x) ((x)&15) +#define S3C_IICCON_SCALEMASK (0xf) + +#define S3C_IICSTAT_MASTER_RX (2<<6) +#define S3C_IICSTAT_MASTER_TX (3<<6) +#define S3C_IICSTAT_SLAVE_RX (0<<6) +#define S3C_IICSTAT_SLAVE_TX (1<<6) +#define S3C_IICSTAT_MODEMASK (3<<6) + +#define S3C_IICSTAT_START (1<<5) +#define S3C_IICSTAT_BUSBUSY (1<<5) +#define S3C_IICSTAT_TXRXEN (1<<4) +#define S3C_IICSTAT_ARBITR (1<<3) +#define S3C_IICSTAT_ASSLAVE (1<<2) +#define S3C_IICSTAT_ADDR0 (1<<1) +#define S3C_IICSTAT_LASTBIT (1<<0) + +#define S3C_IICLC_SDA_DELAY0 (0 << 0) +#define S3C_IICLC_SDA_DELAY5 (1 << 0) +#define S3C_IICLC_SDA_DELAY10 (2 << 0) +#define S3C_IICLC_SDA_DELAY15 (3 << 0) +#define S3C_IICLC_SDA_DELAY_MASK (3 << 0) + +#define S3C_IICLC_FILTER_ON (1<<2) + +/* IIC-bus serial interface */ +struct s3c24xx_i2c_state_s { + i2c_slave slave; + i2c_bus *bus; + target_phys_addr_t base; + qemu_irq irq; + + uint8_t control; + uint8_t status; + uint8_t data; + uint8_t addy; + int busy; + int newstart; +}; + +static void s3c24xx_i2c_irq(struct s3c24xx_i2c_state_s *s) +{ + s->control |= 1 << 4; + + if (s->control & (1 << 5)) { + qemu_irq_raise(s->irq); + } +} + +static void s3c24xx_i2c_reset(struct s3c24xx_i2c_state_s *s) +{ + s->control = 0x00; + s->status = 0x00; + s->busy = 0; + s->newstart = 0; +} + +static void s3c24xx_i2c_event(i2c_slave *i2c, enum i2c_event event) +{ + struct s3c24xx_i2c_state_s *s = (struct s3c24xx_i2c_state_s *) i2c; + + if (!(s->status & (1 << 4))) + return; + + switch (event) { + case I2C_START_RECV: + + case I2C_START_SEND: + s->status |= 1 << 2; + s3c24xx_i2c_irq(s); + break; + + case I2C_FINISH: + s->status &= ~6; + break; + + case I2C_NACK: + s->status |= 1 << 0; + break; + + default: + break; + } +} + +static int s3c24xx_i2c_tx(i2c_slave *i2c, uint8_t data) +{ + struct s3c24xx_i2c_state_s *s = (struct s3c24xx_i2c_state_s *) i2c; + if (!(s->status & (1 << 4))) + return 1; + + if ((s->status >> 6) == 0) + s->data = data; /* TODO */ + + s->status &= ~(1 << 0); + s3c24xx_i2c_irq(s); + + return !(s->control & (1 << 7)); +} + +static int s3c24xx_i2c_rx(i2c_slave *i2c) +{ + struct s3c24xx_i2c_state_s *s = (struct s3c24xx_i2c_state_s *) i2c; + if (!(s->status & (1 << 4))) + return 1; + + if ((s->status >> 6) == 1) { + s->status &= ~(1 << 0); + s3c24xx_i2c_irq(s); + return s->data; + } + + return 0x00; +} + +static void s3c_master_work(void *opaque) +{ + struct s3c24xx_i2c_state_s *s = (struct s3c24xx_i2c_state_s *) opaque; + int start = 0, stop = 0, ack = 1; + + if (s->control & (1 << 4)) /* Interrupt pending */ + return; + if ((s->status & 0x90) != 0x90) /* Master */ + return; + + stop = ~s->status & (1 << 5); + if (s->newstart && s->status & (1 << 5)) { /* START */ + s->busy = 1; + start = 1; + } + s->newstart = 0; + + if (!s->busy) { + return; + } + + if (start) { + ack = !i2c_start_transfer(s->bus, s->data >> 1, (~s->status >> 6) & 1); + } else if (stop) { + i2c_end_transfer(s->bus); + } else if (s->status & (1 << 6)) { + ack = !i2c_send(s->bus, s->data); + } else { + s->data = i2c_recv(s->bus); + + if (!(s->control & (1 << 7))) /* ACK */ + i2c_nack(s->bus); + } + + if (!(s->status & (1 << 5))) { + s->busy = 0; + return; + } + + s->status &= ~1; + s->status |= !ack; + + if (!ack) { + s->busy = 0; + } + s3c24xx_i2c_irq(s); +} + +static uint32_t s3c24xx_i2c_read(void *opaque, target_phys_addr_t addr) +{ + struct s3c24xx_i2c_state_s *s = (struct s3c24xx_i2c_state_s *) opaque; + + switch (addr) { + case S3C_IICCON: + return s->control; + + case S3C_IICSTAT: + return s->status & ~(1 << 5); /* Busy signal */ + + case S3C_IICADD: + return s->addy; + + case S3C_IICDS: + return s->data; + + default: + printf("%s: Bad register 0x%lx\n", __func__, addr); + break; + } + return 0; +} + +static void s3c24xx_i2c_write(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + struct s3c24xx_i2c_state_s *s = (struct s3c24xx_i2c_state_s *) opaque; + + switch (addr) { + case S3C_IICCON: + s->control = (s->control | 0xef) & value; + if (s->busy || ((s->control & (1<<4)) == 0)) + s3c_master_work(s); + break; + + case S3C_IICSTAT: + s->status &= 0x0f; + s->status |= value & 0xf0; + if (s->status & (1 << 5)) + s->newstart = 1; + s3c_master_work(s); + break; + + case S3C_IICADD: + s->addy = value & 0x7f; + i2c_set_slave_address(&s->slave, s->addy); + break; + + case S3C_IICDS: + s->data = value & 0xff; + s->busy = 1; + break; + + default: + printf("%s: Bad register 0x%lx\n", __func__, addr); + break; + } +} + +static CPUReadMemoryFunc *s3c24xx_i2c_readfn[] = { + s3c24xx_i2c_read, + s3c24xx_i2c_read, + s3c24xx_i2c_read, +}; + +static CPUWriteMemoryFunc *s3c24xx_i2c_writefn[] = { + s3c24xx_i2c_write, + s3c24xx_i2c_write, + s3c24xx_i2c_write, +}; + +static void s3c24xx_i2c_save(QEMUFile *f, void *opaque) +{ + struct s3c24xx_i2c_state_s *s = (struct s3c24xx_i2c_state_s *) opaque; + qemu_put_8s(f, &s->control); + qemu_put_8s(f, &s->status); + qemu_put_8s(f, &s->data); + qemu_put_8s(f, &s->addy); + + qemu_put_be32(f, s->busy); + qemu_put_be32(f, s->newstart); + + /* i2c_bus_save(f, s->bus); */ + i2c_slave_save(f, &s->slave); +} + +static int s3c24xx_i2c_load(QEMUFile *f, void *opaque, int version_id) +{ + struct s3c24xx_i2c_state_s *s = (struct s3c24xx_i2c_state_s *) opaque; + qemu_get_8s(f, &s->control); + qemu_get_8s(f, &s->status); + qemu_get_8s(f, &s->data); + qemu_get_8s(f, &s->addy); + + s->busy = qemu_get_be32(f); + s->newstart = qemu_get_be32(f); + + /* i2c_bus_load(f, s->bus); */ + i2c_slave_load(f, &s->slave); + return 0; +} + +void s3c24xx_iic_init(S3CState *soc, target_phys_addr_t base_addr) +{ + int iomemtype; + struct s3c24xx_i2c_state_s *s = qemu_mallocz(sizeof(struct s3c24xx_i2c_state_s)); + + s->base = base_addr; + s->irq = soc->irqs[27]; + s->slave.event = s3c24xx_i2c_event; + s->slave.send = s3c24xx_i2c_tx; + s->slave.recv = s3c24xx_i2c_rx; + s->bus = i2c_init_bus(); + + s3c24xx_i2c_reset(s); + + iomemtype = cpu_register_io_memory(0, s3c24xx_i2c_readfn, + s3c24xx_i2c_writefn, s); + cpu_register_physical_memory(base_addr, 0xffffff, iomemtype); + + register_savevm("s3c24xx_i2c", 0, 0, s3c24xx_i2c_save, s3c24xx_i2c_load, s); + + soc->iic = s; +} + +i2c_bus *s3c24xx_i2c_bus(struct s3c24xx_i2c_state_s *s) +{ + return s->bus; +} + -- Regards Vincent http://www.kyllikki.org/