qemu/hw/i2c/exynos4210_i2c.c
<<
>>
Prefs
   1/*
   2 *  Exynos4210 I2C Bus Serial Interface Emulation
   3 *
   4 *  Copyright (C) 2012 Samsung Electronics Co Ltd.
   5 *    Maksim Kozlov, <m.kozlov@samsung.com>
   6 *    Igor Mitsyanko, <i.mitsyanko@samsung.com>
   7 *
   8 *  This program is free software; you can redistribute it and/or modify it
   9 *  under the terms of the GNU General Public License as published by the
  10 *  Free Software Foundation; either version 2 of the License, or
  11 *  (at your option) any later version.
  12 *
  13 *  This program is distributed in the hope that it will be useful, but WITHOUT
  14 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15 *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  16 *  for more details.
  17 *
  18 *  You should have received a copy of the GNU General Public License along
  19 *  with this program; if not, see <http://www.gnu.org/licenses/>.
  20 *
  21 */
  22
  23#include "qemu/osdep.h"
  24#include "qemu/module.h"
  25#include "qemu/timer.h"
  26#include "hw/sysbus.h"
  27#include "hw/i2c/i2c.h"
  28
  29#ifndef EXYNOS4_I2C_DEBUG
  30#define EXYNOS4_I2C_DEBUG                 0
  31#endif
  32
  33#define TYPE_EXYNOS4_I2C                  "exynos4210.i2c"
  34#define EXYNOS4_I2C(obj)                  \
  35    OBJECT_CHECK(Exynos4210I2CState, (obj), TYPE_EXYNOS4_I2C)
  36
  37/* Exynos4210 I2C memory map */
  38#define EXYNOS4_I2C_MEM_SIZE              0x14
  39#define I2CCON_ADDR                       0x00  /* control register */
  40#define I2CSTAT_ADDR                      0x04  /* control/status register */
  41#define I2CADD_ADDR                       0x08  /* address register */
  42#define I2CDS_ADDR                        0x0c  /* data shift register */
  43#define I2CLC_ADDR                        0x10  /* line control register */
  44
  45#define I2CCON_ACK_GEN                    (1 << 7)
  46#define I2CCON_INTRS_EN                   (1 << 5)
  47#define I2CCON_INT_PEND                   (1 << 4)
  48
  49#define EXYNOS4_I2C_MODE(reg)             (((reg) >> 6) & 3)
  50#define I2C_IN_MASTER_MODE(reg)           (((reg) >> 6) & 2)
  51#define I2CMODE_MASTER_Rx                 0x2
  52#define I2CMODE_MASTER_Tx                 0x3
  53#define I2CSTAT_LAST_BIT                  (1 << 0)
  54#define I2CSTAT_OUTPUT_EN                 (1 << 4)
  55#define I2CSTAT_START_BUSY                (1 << 5)
  56
  57
  58#if EXYNOS4_I2C_DEBUG
  59#define DPRINT(fmt, args...)              \
  60    do { fprintf(stderr, "QEMU I2C: "fmt, ## args); } while (0)
  61
  62static const char *exynos4_i2c_get_regname(unsigned offset)
  63{
  64    switch (offset) {
  65    case I2CCON_ADDR:
  66        return "I2CCON";
  67    case I2CSTAT_ADDR:
  68        return "I2CSTAT";
  69    case I2CADD_ADDR:
  70        return "I2CADD";
  71    case I2CDS_ADDR:
  72        return "I2CDS";
  73    case I2CLC_ADDR:
  74        return "I2CLC";
  75    default:
  76        return "[?]";
  77    }
  78}
  79
  80#else
  81#define DPRINT(fmt, args...)              do { } while (0)
  82#endif
  83
  84typedef struct Exynos4210I2CState {
  85    SysBusDevice parent_obj;
  86
  87    MemoryRegion iomem;
  88    I2CBus *bus;
  89    qemu_irq irq;
  90
  91    uint8_t i2ccon;
  92    uint8_t i2cstat;
  93    uint8_t i2cadd;
  94    uint8_t i2cds;
  95    uint8_t i2clc;
  96    bool scl_free;
  97} Exynos4210I2CState;
  98
  99static inline void exynos4210_i2c_raise_interrupt(Exynos4210I2CState *s)
 100{
 101    if (s->i2ccon & I2CCON_INTRS_EN) {
 102        s->i2ccon |= I2CCON_INT_PEND;
 103        qemu_irq_raise(s->irq);
 104    }
 105}
 106
 107static void exynos4210_i2c_data_receive(void *opaque)
 108{
 109    Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
 110
 111    s->i2cstat &= ~I2CSTAT_LAST_BIT;
 112    s->scl_free = false;
 113    s->i2cds = i2c_recv(s->bus);
 114    exynos4210_i2c_raise_interrupt(s);
 115}
 116
 117static void exynos4210_i2c_data_send(void *opaque)
 118{
 119    Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
 120
 121    s->i2cstat &= ~I2CSTAT_LAST_BIT;
 122    s->scl_free = false;
 123    if (i2c_send(s->bus, s->i2cds) < 0 && (s->i2ccon & I2CCON_ACK_GEN)) {
 124        s->i2cstat |= I2CSTAT_LAST_BIT;
 125    }
 126    exynos4210_i2c_raise_interrupt(s);
 127}
 128
 129static uint64_t exynos4210_i2c_read(void *opaque, hwaddr offset,
 130                                 unsigned size)
 131{
 132    Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
 133    uint8_t value;
 134
 135    switch (offset) {
 136    case I2CCON_ADDR:
 137        value = s->i2ccon;
 138        break;
 139    case I2CSTAT_ADDR:
 140        value = s->i2cstat;
 141        break;
 142    case I2CADD_ADDR:
 143        value = s->i2cadd;
 144        break;
 145    case I2CDS_ADDR:
 146        value = s->i2cds;
 147        s->scl_free = true;
 148        if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx &&
 149               (s->i2cstat & I2CSTAT_START_BUSY) &&
 150               !(s->i2ccon & I2CCON_INT_PEND)) {
 151            exynos4210_i2c_data_receive(s);
 152        }
 153        break;
 154    case I2CLC_ADDR:
 155        value = s->i2clc;
 156        break;
 157    default:
 158        value = 0;
 159        DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset);
 160        break;
 161    }
 162
 163    DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_get_regname(offset),
 164            (unsigned int)offset, value);
 165    return value;
 166}
 167
 168static void exynos4210_i2c_write(void *opaque, hwaddr offset,
 169                              uint64_t value, unsigned size)
 170{
 171    Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
 172    uint8_t v = value & 0xff;
 173
 174    DPRINT("write %s [0x%02x] <- 0x%02x\n", exynos4_i2c_get_regname(offset),
 175            (unsigned int)offset, v);
 176
 177    switch (offset) {
 178    case I2CCON_ADDR:
 179        s->i2ccon = (v & ~I2CCON_INT_PEND) | (s->i2ccon & I2CCON_INT_PEND);
 180        if ((s->i2ccon & I2CCON_INT_PEND) && !(v & I2CCON_INT_PEND)) {
 181            s->i2ccon &= ~I2CCON_INT_PEND;
 182            qemu_irq_lower(s->irq);
 183            if (!(s->i2ccon & I2CCON_INTRS_EN)) {
 184                s->i2cstat &= ~I2CSTAT_START_BUSY;
 185            }
 186
 187            if (s->i2cstat & I2CSTAT_START_BUSY) {
 188                if (s->scl_free) {
 189                    if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) {
 190                        exynos4210_i2c_data_send(s);
 191                    } else if (EXYNOS4_I2C_MODE(s->i2cstat) ==
 192                            I2CMODE_MASTER_Rx) {
 193                        exynos4210_i2c_data_receive(s);
 194                    }
 195                } else {
 196                    s->i2ccon |= I2CCON_INT_PEND;
 197                    qemu_irq_raise(s->irq);
 198                }
 199            }
 200        }
 201        break;
 202    case I2CSTAT_ADDR:
 203        s->i2cstat =
 204                (s->i2cstat & I2CSTAT_START_BUSY) | (v & ~I2CSTAT_START_BUSY);
 205
 206        if (!(s->i2cstat & I2CSTAT_OUTPUT_EN)) {
 207            s->i2cstat &= ~I2CSTAT_START_BUSY;
 208            s->scl_free = true;
 209            qemu_irq_lower(s->irq);
 210            break;
 211        }
 212
 213        /* Nothing to do if in i2c slave mode */
 214        if (!I2C_IN_MASTER_MODE(s->i2cstat)) {
 215            break;
 216        }
 217
 218        if (v & I2CSTAT_START_BUSY) {
 219            s->i2cstat &= ~I2CSTAT_LAST_BIT;
 220            s->i2cstat |= I2CSTAT_START_BUSY;    /* Line is busy */
 221            s->scl_free = false;
 222
 223            /* Generate start bit and send slave address */
 224            if (i2c_start_transfer(s->bus, s->i2cds >> 1, s->i2cds & 0x1) &&
 225                    (s->i2ccon & I2CCON_ACK_GEN)) {
 226                s->i2cstat |= I2CSTAT_LAST_BIT;
 227            } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) {
 228                exynos4210_i2c_data_receive(s);
 229            }
 230            exynos4210_i2c_raise_interrupt(s);
 231        } else {
 232            i2c_end_transfer(s->bus);
 233            if (!(s->i2ccon & I2CCON_INT_PEND)) {
 234                s->i2cstat &= ~I2CSTAT_START_BUSY;
 235            }
 236            s->scl_free = true;
 237        }
 238        break;
 239    case I2CADD_ADDR:
 240        if ((s->i2cstat & I2CSTAT_OUTPUT_EN) == 0) {
 241            s->i2cadd = v;
 242        }
 243        break;
 244    case I2CDS_ADDR:
 245        if (s->i2cstat & I2CSTAT_OUTPUT_EN) {
 246            s->i2cds = v;
 247            s->scl_free = true;
 248            if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx &&
 249                    (s->i2cstat & I2CSTAT_START_BUSY) &&
 250                    !(s->i2ccon & I2CCON_INT_PEND)) {
 251                exynos4210_i2c_data_send(s);
 252            }
 253        }
 254        break;
 255    case I2CLC_ADDR:
 256        s->i2clc = v;
 257        break;
 258    default:
 259        DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset);
 260        break;
 261    }
 262}
 263
 264static const MemoryRegionOps exynos4210_i2c_ops = {
 265    .read = exynos4210_i2c_read,
 266    .write = exynos4210_i2c_write,
 267    .endianness = DEVICE_NATIVE_ENDIAN,
 268};
 269
 270static const VMStateDescription exynos4210_i2c_vmstate = {
 271    .name = "exynos4210.i2c",
 272    .version_id = 1,
 273    .minimum_version_id = 1,
 274    .fields = (VMStateField[]) {
 275        VMSTATE_UINT8(i2ccon, Exynos4210I2CState),
 276        VMSTATE_UINT8(i2cstat, Exynos4210I2CState),
 277        VMSTATE_UINT8(i2cds, Exynos4210I2CState),
 278        VMSTATE_UINT8(i2cadd, Exynos4210I2CState),
 279        VMSTATE_UINT8(i2clc, Exynos4210I2CState),
 280        VMSTATE_BOOL(scl_free, Exynos4210I2CState),
 281        VMSTATE_END_OF_LIST()
 282    }
 283};
 284
 285static void exynos4210_i2c_reset(DeviceState *d)
 286{
 287    Exynos4210I2CState *s = EXYNOS4_I2C(d);
 288
 289    s->i2ccon  = 0x00;
 290    s->i2cstat = 0x00;
 291    s->i2cds   = 0xFF;
 292    s->i2clc   = 0x00;
 293    s->i2cadd  = 0xFF;
 294    s->scl_free = true;
 295}
 296
 297static void exynos4210_i2c_init(Object *obj)
 298{
 299    DeviceState *dev = DEVICE(obj);
 300    Exynos4210I2CState *s = EXYNOS4_I2C(obj);
 301    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
 302
 303    memory_region_init_io(&s->iomem, obj, &exynos4210_i2c_ops, s,
 304                          TYPE_EXYNOS4_I2C, EXYNOS4_I2C_MEM_SIZE);
 305    sysbus_init_mmio(sbd, &s->iomem);
 306    sysbus_init_irq(sbd, &s->irq);
 307    s->bus = i2c_init_bus(dev, "i2c");
 308}
 309
 310static void exynos4210_i2c_class_init(ObjectClass *klass, void *data)
 311{
 312    DeviceClass *dc = DEVICE_CLASS(klass);
 313
 314    dc->vmsd = &exynos4210_i2c_vmstate;
 315    dc->reset = exynos4210_i2c_reset;
 316}
 317
 318static const TypeInfo exynos4210_i2c_type_info = {
 319    .name = TYPE_EXYNOS4_I2C,
 320    .parent = TYPE_SYS_BUS_DEVICE,
 321    .instance_size = sizeof(Exynos4210I2CState),
 322    .instance_init = exynos4210_i2c_init,
 323    .class_init = exynos4210_i2c_class_init,
 324};
 325
 326static void exynos4210_i2c_register_types(void)
 327{
 328    type_register_static(&exynos4210_i2c_type_info);
 329}
 330
 331type_init(exynos4210_i2c_register_types)
 332