| /* |
| * PPC4xx I2C controller emulation |
| * |
| * Copyright (c) 2007 Jocelyn Mayer |
| * Copyright (c) 2012 François Revol |
| * Copyright (c) 2016-2018 BALATON Zoltan |
| * |
| * 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 "qemu/osdep.h" |
| #include "qemu/log.h" |
| #include "qemu/module.h" |
| #include "hw/i2c/ppc4xx_i2c.h" |
| #include "hw/irq.h" |
| |
| #define PPC4xx_I2C_MEM_SIZE 18 |
| |
| enum { |
| IIC_MDBUF = 0, |
| /* IIC_SDBUF = 2, */ |
| IIC_LMADR = 4, |
| IIC_HMADR, |
| IIC_CNTL, |
| IIC_MDCNTL, |
| IIC_STS, |
| IIC_EXTSTS, |
| IIC_LSADR, |
| IIC_HSADR, |
| IIC_CLKDIV, |
| IIC_INTRMSK, |
| IIC_XFRCNT, |
| IIC_XTCNTLSS, |
| IIC_DIRECTCNTL |
| /* IIC_INTR */ |
| }; |
| |
| #define IIC_CNTL_PT (1 << 0) |
| #define IIC_CNTL_READ (1 << 1) |
| #define IIC_CNTL_CHT (1 << 2) |
| #define IIC_CNTL_RPST (1 << 3) |
| #define IIC_CNTL_AMD (1 << 6) |
| #define IIC_CNTL_HMT (1 << 7) |
| |
| #define IIC_MDCNTL_EINT (1 << 2) |
| #define IIC_MDCNTL_ESM (1 << 3) |
| #define IIC_MDCNTL_FMDB (1 << 6) |
| |
| #define IIC_STS_PT (1 << 0) |
| #define IIC_STS_IRQA (1 << 1) |
| #define IIC_STS_ERR (1 << 2) |
| #define IIC_STS_MDBF (1 << 4) |
| #define IIC_STS_MDBS (1 << 5) |
| |
| #define IIC_EXTSTS_XFRA (1 << 0) |
| #define IIC_EXTSTS_BCS_FREE (4 << 4) |
| #define IIC_EXTSTS_BCS_BUSY (5 << 4) |
| |
| #define IIC_INTRMSK_EIMTC (1 << 0) |
| #define IIC_INTRMSK_EITA (1 << 1) |
| #define IIC_INTRMSK_EIIC (1 << 2) |
| #define IIC_INTRMSK_EIHE (1 << 3) |
| |
| #define IIC_XTCNTLSS_SRST (1 << 0) |
| |
| #define IIC_DIRECTCNTL_SDAC (1 << 3) |
| #define IIC_DIRECTCNTL_SCLC (1 << 2) |
| #define IIC_DIRECTCNTL_MSDA (1 << 1) |
| #define IIC_DIRECTCNTL_MSCL (1 << 0) |
| |
| static void ppc4xx_i2c_reset(DeviceState *s) |
| { |
| PPC4xxI2CState *i2c = PPC4xx_I2C(s); |
| |
| i2c->mdidx = -1; |
| memset(i2c->mdata, 0, ARRAY_SIZE(i2c->mdata)); |
| /* [hl][ms]addr are not affected by reset */ |
| i2c->cntl = 0; |
| i2c->mdcntl = 0; |
| i2c->sts = 0; |
| i2c->extsts = IIC_EXTSTS_BCS_FREE; |
| i2c->clkdiv = 0; |
| i2c->intrmsk = 0; |
| i2c->xfrcnt = 0; |
| i2c->xtcntlss = 0; |
| i2c->directcntl = 0xf; /* all non-reserved bits set */ |
| } |
| |
| static uint64_t ppc4xx_i2c_readb(void *opaque, hwaddr addr, unsigned int size) |
| { |
| PPC4xxI2CState *i2c = PPC4xx_I2C(opaque); |
| uint64_t ret; |
| int i; |
| |
| switch (addr) { |
| case IIC_MDBUF: |
| if (i2c->mdidx < 0) { |
| ret = 0xff; |
| break; |
| } |
| ret = i2c->mdata[0]; |
| if (i2c->mdidx == 3) { |
| i2c->sts &= ~IIC_STS_MDBF; |
| } else if (i2c->mdidx == 0) { |
| i2c->sts &= ~IIC_STS_MDBS; |
| } |
| for (i = 0; i < i2c->mdidx; i++) { |
| i2c->mdata[i] = i2c->mdata[i + 1]; |
| } |
| if (i2c->mdidx >= 0) { |
| i2c->mdidx--; |
| } |
| break; |
| case IIC_LMADR: |
| ret = i2c->lmadr; |
| break; |
| case IIC_HMADR: |
| ret = i2c->hmadr; |
| break; |
| case IIC_CNTL: |
| ret = i2c->cntl; |
| break; |
| case IIC_MDCNTL: |
| ret = i2c->mdcntl; |
| break; |
| case IIC_STS: |
| ret = i2c->sts; |
| break; |
| case IIC_EXTSTS: |
| ret = i2c_bus_busy(i2c->bus) ? |
| IIC_EXTSTS_BCS_BUSY : IIC_EXTSTS_BCS_FREE; |
| break; |
| case IIC_LSADR: |
| ret = i2c->lsadr; |
| break; |
| case IIC_HSADR: |
| ret = i2c->hsadr; |
| break; |
| case IIC_CLKDIV: |
| ret = i2c->clkdiv; |
| break; |
| case IIC_INTRMSK: |
| ret = i2c->intrmsk; |
| break; |
| case IIC_XFRCNT: |
| ret = i2c->xfrcnt; |
| break; |
| case IIC_XTCNTLSS: |
| ret = i2c->xtcntlss; |
| break; |
| case IIC_DIRECTCNTL: |
| ret = i2c->directcntl; |
| break; |
| default: |
| if (addr < PPC4xx_I2C_MEM_SIZE) { |
| qemu_log_mask(LOG_UNIMP, "%s: Unimplemented register 0x%" |
| HWADDR_PRIx "\n", __func__, addr); |
| } else { |
| qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad address 0x%" |
| HWADDR_PRIx "\n", __func__, addr); |
| } |
| ret = 0; |
| break; |
| } |
| return ret; |
| } |
| |
| static void ppc4xx_i2c_writeb(void *opaque, hwaddr addr, uint64_t value, |
| unsigned int size) |
| { |
| PPC4xxI2CState *i2c = opaque; |
| |
| switch (addr) { |
| case IIC_MDBUF: |
| if (i2c->mdidx >= 3) { |
| break; |
| } |
| i2c->mdata[++i2c->mdidx] = value; |
| if (i2c->mdidx == 3) { |
| i2c->sts |= IIC_STS_MDBF; |
| } else if (i2c->mdidx == 0) { |
| i2c->sts |= IIC_STS_MDBS; |
| } |
| break; |
| case IIC_LMADR: |
| i2c->lmadr = value; |
| break; |
| case IIC_HMADR: |
| i2c->hmadr = value; |
| break; |
| case IIC_CNTL: |
| i2c->cntl = value & ~IIC_CNTL_PT; |
| if (value & IIC_CNTL_AMD) { |
| qemu_log_mask(LOG_UNIMP, "%s: only 7 bit addresses supported\n", |
| __func__); |
| } |
| if (value & IIC_CNTL_HMT && i2c_bus_busy(i2c->bus)) { |
| i2c_end_transfer(i2c->bus); |
| if (i2c->mdcntl & IIC_MDCNTL_EINT && |
| i2c->intrmsk & IIC_INTRMSK_EIHE) { |
| i2c->sts |= IIC_STS_IRQA; |
| qemu_irq_raise(i2c->irq); |
| } |
| } else if (value & IIC_CNTL_PT) { |
| int recv = (value & IIC_CNTL_READ) >> 1; |
| int tct = value >> 4 & 3; |
| int i; |
| |
| if (recv && (i2c->lmadr >> 1) >= 0x50 && (i2c->lmadr >> 1) < 0x58) { |
| /* smbus emulation does not like multi byte reads w/o restart */ |
| value |= IIC_CNTL_RPST; |
| } |
| |
| for (i = 0; i <= tct; i++) { |
| if (!i2c_bus_busy(i2c->bus)) { |
| i2c->extsts = IIC_EXTSTS_BCS_FREE; |
| if (i2c_start_transfer(i2c->bus, i2c->lmadr >> 1, recv)) { |
| i2c->sts |= IIC_STS_ERR; |
| i2c->extsts |= IIC_EXTSTS_XFRA; |
| break; |
| } else { |
| i2c->sts &= ~IIC_STS_ERR; |
| } |
| } |
| if (!(i2c->sts & IIC_STS_ERR) && |
| i2c_send_recv(i2c->bus, &i2c->mdata[i], !recv)) { |
| i2c->sts |= IIC_STS_ERR; |
| i2c->extsts |= IIC_EXTSTS_XFRA; |
| break; |
| } |
| if (value & IIC_CNTL_RPST || !(value & IIC_CNTL_CHT)) { |
| i2c_end_transfer(i2c->bus); |
| } |
| } |
| i2c->xfrcnt = i; |
| i2c->mdidx = i - 1; |
| if (recv && i2c->mdidx >= 0) { |
| i2c->sts |= IIC_STS_MDBS; |
| } |
| if (recv && i2c->mdidx == 3) { |
| i2c->sts |= IIC_STS_MDBF; |
| } |
| if (i && i2c->mdcntl & IIC_MDCNTL_EINT && |
| i2c->intrmsk & IIC_INTRMSK_EIMTC) { |
| i2c->sts |= IIC_STS_IRQA; |
| qemu_irq_raise(i2c->irq); |
| } |
| } |
| break; |
| case IIC_MDCNTL: |
| i2c->mdcntl = value & 0x3d; |
| if (value & IIC_MDCNTL_ESM) { |
| qemu_log_mask(LOG_UNIMP, "%s: slave mode not implemented\n", |
| __func__); |
| } |
| if (value & IIC_MDCNTL_FMDB) { |
| i2c->mdidx = -1; |
| memset(i2c->mdata, 0, ARRAY_SIZE(i2c->mdata)); |
| i2c->sts &= ~(IIC_STS_MDBF | IIC_STS_MDBS); |
| } |
| break; |
| case IIC_STS: |
| i2c->sts &= ~(value & 0x0a); |
| if (value & IIC_STS_IRQA && i2c->mdcntl & IIC_MDCNTL_EINT) { |
| qemu_irq_lower(i2c->irq); |
| } |
| break; |
| case IIC_EXTSTS: |
| i2c->extsts &= ~(value & 0x8f); |
| break; |
| case IIC_LSADR: |
| i2c->lsadr = value; |
| break; |
| case IIC_HSADR: |
| i2c->hsadr = value; |
| break; |
| case IIC_CLKDIV: |
| i2c->clkdiv = value; |
| break; |
| case IIC_INTRMSK: |
| i2c->intrmsk = value; |
| break; |
| case IIC_XFRCNT: |
| i2c->xfrcnt = value & 0x77; |
| break; |
| case IIC_XTCNTLSS: |
| i2c->xtcntlss &= ~(value & 0xf0); |
| if (value & IIC_XTCNTLSS_SRST) { |
| /* Is it actually a full reset? U-Boot sets some regs before */ |
| ppc4xx_i2c_reset(DEVICE(i2c)); |
| break; |
| } |
| break; |
| case IIC_DIRECTCNTL: |
| i2c->directcntl = value & (IIC_DIRECTCNTL_SDAC & IIC_DIRECTCNTL_SCLC); |
| i2c->directcntl |= (value & IIC_DIRECTCNTL_SCLC ? 1 : 0); |
| bitbang_i2c_set(&i2c->bitbang, BITBANG_I2C_SCL, |
| i2c->directcntl & IIC_DIRECTCNTL_MSCL); |
| i2c->directcntl |= bitbang_i2c_set(&i2c->bitbang, BITBANG_I2C_SDA, |
| (value & IIC_DIRECTCNTL_SDAC) != 0) << 1; |
| break; |
| default: |
| if (addr < PPC4xx_I2C_MEM_SIZE) { |
| qemu_log_mask(LOG_UNIMP, "%s: Unimplemented register 0x%" |
| HWADDR_PRIx "\n", __func__, addr); |
| } else { |
| qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad address 0x%" |
| HWADDR_PRIx "\n", __func__, addr); |
| } |
| break; |
| } |
| } |
| |
| static const MemoryRegionOps ppc4xx_i2c_ops = { |
| .read = ppc4xx_i2c_readb, |
| .write = ppc4xx_i2c_writeb, |
| .valid.min_access_size = 1, |
| .valid.max_access_size = 4, |
| .impl.min_access_size = 1, |
| .impl.max_access_size = 1, |
| .endianness = DEVICE_NATIVE_ENDIAN, |
| }; |
| |
| static void ppc4xx_i2c_init(Object *o) |
| { |
| PPC4xxI2CState *s = PPC4xx_I2C(o); |
| |
| memory_region_init_io(&s->iomem, OBJECT(s), &ppc4xx_i2c_ops, s, |
| TYPE_PPC4xx_I2C, PPC4xx_I2C_MEM_SIZE); |
| sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); |
| sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq); |
| s->bus = i2c_init_bus(DEVICE(s), "i2c"); |
| bitbang_i2c_init(&s->bitbang, s->bus); |
| } |
| |
| static void ppc4xx_i2c_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->reset = ppc4xx_i2c_reset; |
| } |
| |
| static const TypeInfo ppc4xx_i2c_type_info = { |
| .name = TYPE_PPC4xx_I2C, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(PPC4xxI2CState), |
| .instance_init = ppc4xx_i2c_init, |
| .class_init = ppc4xx_i2c_class_init, |
| }; |
| |
| static void ppc4xx_i2c_register_types(void) |
| { |
| type_register_static(&ppc4xx_i2c_type_info); |
| } |
| |
| type_init(ppc4xx_i2c_register_types) |