qemu/hw/misc/si57x.c
<<
>>
Prefs
   1/*
   2 * SI570,SI571 Dummy Crystal Oscillator
   3 *
   4 * Copyright (c) 2016 Xilinx Inc.
   5 * Written by Sai Pavan Boddu <saipava@xilinx.com>
   6 *
   7 * This program is free software; you can redistribute it and/or modify it
   8 * under the terms of the GNU General Public License as published by the
   9 * Free Software Foundation; either version 2 of the License, or
  10 * (at your option) any later version.
  11 *
  12 * This program is distributed in the hope that it will be useful, but WITHOUT
  13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  15 * for more details.
  16 *
  17 * You should have received a copy of the GNU General Public License along
  18 * with this program; if not, see <http://www.gnu.org/licenses/>.
  19 */
  20
  21#include "qemu/osdep.h"
  22#include "qemu/log.h"
  23#include "hw/i2c/i2c.h"
  24
  25#ifndef SI57X_DEBUG
  26#define SI57X_DEBUG 0
  27#endif
  28
  29#define DPRINT(fmt, args...) \
  30    do { \
  31        if (SI57X_DEBUG) { \
  32            qemu_log("%s: "fmt, __func__, ## args); \
  33        } \
  34    } while (0)
  35
  36#define HS_DIV_OFFSET 5
  37#define HS_DIV_MASK   0xE0
  38
  39#define N1_DIV_MSB_OFFSET 6
  40#define N1_DIV_MSB_MASK   0x3F
  41
  42#define N1_DIV_LSB_OFFSET 0
  43#define N1_DIV_LSB_MASK   0xC0
  44
  45#define CTRL_REG0   135
  46#define CTRL_REG1   137
  47
  48#define REG0 0
  49#define REG1 1
  50#define REG2 2
  51#define REG3 3
  52#define REG4 4
  53#define REG5 5
  54#define REG6 6
  55#define REG7 7
  56
  57/* Mapping CTRL_REG0/1 to offset 6,7 */
  58#define CTRL_REG0_REL   6
  59    #define CTRL_REG0_RST_REG   7
  60    #define CTRL_REG0_NEWFREQ   6
  61    #define CTRL_REG0_FREZ_M    5
  62    #define CTRL_REG0_FREZ_VCDC 4
  63    #define CTRL_REG0_RECALL    0
  64#define CTRL_REG1_REL   7
  65    #define CTRL_REG1_FREZ_DCO  4
  66
  67#define M(x) (1 << x)
  68
  69#define TYPE_SI57X "si57x"
  70#define SI57X(obj) \
  71    OBJECT_CHECK(Si57xState, (obj), TYPE_SI57X)
  72
  73typedef struct Si57xState {
  74    /* <private> */
  75    I2CSlave parent_obj;
  76
  77    /* <public> */
  78    uint64_t rfreq; /* RFREQ Multiplier */
  79    /* HS_DIV, N1 Dividers */
  80    uint16_t hs_div;
  81    uint16_t n1;
  82    /* Fxtal is not needed as it cannot be read */
  83
  84    /* Temperature Stability */
  85    uint16_t temp_stab;
  86    uint8_t regs[8];
  87    uint8_t state;
  88    uint8_t ptr;
  89} Si57xState;
  90
  91enum states {
  92    IDEAL,
  93    ADDRESSING,
  94    ADDRESSING_DONE,
  95    WRITING,
  96    READING,
  97};
  98
  99enum temp_stability {
 100    TEMP_STAB_7PPM = 7,
 101    TEMP_STAB_20PPM = 20,
 102    TEMP_STAB_50PPM = 50,
 103};
 104
 105static bool rfreq_is_updating(Si57xState *s)
 106{
 107    uint8_t addr = s->ptr;
 108
 109    /* Reg 1 to 5 Belogs to RFREQ */
 110    if (addr > REG0) {
 111        if (addr == REG1) {
 112            /* Only Bits 0 to 5 of REG1 belongs to RFREQ */
 113            return (s->regs[addr] & 0x3F) ? true : false;
 114        }
 115        return true;
 116    }
 117    return false;
 118}
 119
 120/* Issue warnings when the required fields are updated without asserting
 121 * Freez functionality.
 122 */
 123static void si57x_freez_filter(Si57xState *s, int data)
 124{
 125    if (rfreq_is_updating(s)) {
 126        /* If RFREQ is updating, make sure FREEZ_M or FREEZ_DCO is high */
 127        if ((s->regs[CTRL_REG0_REL] & M(CTRL_REG0_FREZ_M)) ||
 128            (s->regs[CTRL_REG1_REL] & M(CTRL_REG1_FREZ_DCO))) {
 129            DPRINT("Update RFREQ 0x%x\n", data);
 130        } else {
 131            qemu_log_mask(LOG_GUEST_ERROR, "Update RFREQ without asserting"
 132                          " FREEZ_M/FREEZ_DCO\n");
 133        }
 134    } else {
 135        /* If HS_DIV, N1 are updating, make sure FREEZ_DCO is high */
 136        if (!(s->regs[CTRL_REG1_REL] & M(CTRL_REG1_FREZ_DCO))) {
 137            qemu_log_mask(LOG_GUEST_ERROR, "Updateing HS_DIV/N1 without"
 138                          " FREEZ_DCO assert\n");
 139        }
 140    }
 141}
 142
 143static void si57x_reset(DeviceState *dev)
 144{
 145    Si57xState *s = SI57X(dev);
 146    /* Fill Fxtal, HD_DIV, N1 with example values as the default values are not
 147     * specified in the documentation. Use the example values as mentioned in:
 148     * https://www.silabs.com/Support%20Documents/TechnicalDocs/si570.pdf
 149     */
 150    /* HS_DIV = 0 */
 151    s->regs[REG0] = 0;
 152
 153    /* N1_DIV = 0x7 */
 154    s->regs[REG1] = 0x3 << N1_DIV_LSB_OFFSET;
 155    s->regs[REG0] |= 0x1 << N1_DIV_MSB_OFFSET;
 156
 157    /* RFREQ = 0x2BC011EB8 */
 158    s->regs[REG5] = 0xB8;
 159    s->regs[REG4] = 0x1E;
 160    s->regs[REG3] = 0x01;
 161    s->regs[REG2] = 0xBC;
 162    s->regs[REG1] |= 0x2;
 163
 164    s->regs[CTRL_REG0_REL] &= ~M(CTRL_REG0_RST_REG);
 165    /* By combining HS_DIV, N1 and RFREQ the user can calculate Fxtal.
 166     * We can assume the default Fxtal, i.e  114.285000000 MHz
 167     */
 168}
 169
 170static void si57x_ctrl0_pw(Si57xState *s)
 171{
 172    if (s->regs[CTRL_REG0_REL] & M(CTRL_REG0_RST_REG)) {
 173        si57x_reset(DEVICE(s));
 174    }
 175    s->regs[CTRL_REG0_REL] &= ~M(CTRL_REG0_NEWFREQ);
 176    s->regs[CTRL_REG1_REL] &= ~M(CTRL_REG1_FREZ_DCO);
 177}
 178
 179/* SI57X registers are distributed at address 7-12, 13-18, 135, 137.
 180 * So we remap them internally to offset 0 to 7.
 181 * This function maps the registers for devices having Temperature stability of
 182 * 50PPM, 20PPM and 7PPM.
 183 */
 184static void si57x_set_addr(Si57xState *s, uint8_t addr)
 185{
 186    if (addr > 18) {
 187        switch (addr) {
 188        case CTRL_REG0:
 189            s->ptr = 6;
 190            break;
 191        case CTRL_REG1:
 192            s->ptr = 7;
 193            break;
 194        }
 195        DPRINT("Setting ptr to %d\n", s->ptr);
 196        return;
 197    }
 198
 199    switch (s->temp_stab) {
 200    case TEMP_STAB_50PPM:
 201    case TEMP_STAB_20PPM:
 202        s->ptr = addr - 7;
 203        break;
 204    case TEMP_STAB_7PPM:
 205        s->ptr = addr - 13;
 206        break;
 207    }
 208    DPRINT("Setting ptr to %d\n", s->ptr);
 209}
 210
 211/* Master Tx i.e Slave Rx */
 212static int si57x_tx(I2CSlave *s, uint8_t data)
 213{
 214    Si57xState *slave = SI57X(s);
 215    uint8_t addr;
 216
 217    if (slave->state == ADDRESSING) {
 218        DPRINT("addr: 0x%x\n", data);
 219        si57x_set_addr(slave, data);
 220        slave->state = ADDRESSING_DONE;
 221    } else {
 222        DPRINT("data: 0x%x\n", data);
 223        slave->state = WRITING;
 224        addr = slave->ptr;
 225        if (addr < 6) {
 226            si57x_freez_filter(slave, data);
 227            slave->regs[addr] = data;
 228            slave->ptr++;
 229        } else {
 230            switch (addr) {
 231            case CTRL_REG0_REL:
 232                slave->regs[addr] = data;
 233                si57x_ctrl0_pw(slave);
 234                break;
 235            case CTRL_REG1_REL:
 236                slave->regs[addr] = data;
 237                break;
 238            }
 239        }
 240    }
 241
 242    return 0;
 243}
 244
 245/* Master Rx i.e Slave Tx */
 246static int si57x_rx(I2CSlave *s)
 247{
 248    Si57xState *slave = SI57X(s);
 249
 250    DPRINT("data: 0x%x\n", slave->regs[slave->ptr]);
 251
 252    return slave->regs[slave->ptr];
 253}
 254
 255static void si57x_event(I2CSlave *i2c, enum i2c_event event)
 256{
 257    Si57xState *s = SI57X(i2c);
 258
 259    switch (event) {
 260    case I2C_START_SEND:
 261        s->state = ADDRESSING;
 262        break;
 263    case I2C_START_RECV:
 264        s->state = READING;
 265        break;
 266    case I2C_FINISH:
 267    case I2C_NACK:
 268        s->state = IDEAL;
 269        break;
 270    }
 271}
 272
 273static int si57x_init(I2CSlave *i2c)
 274{
 275    /* Nothing to do */
 276    return 0;
 277}
 278
 279static Property si57x_properties[] = {
 280    DEFINE_PROP_UINT16("temperature-stability", Si57xState, temp_stab,
 281                       TEMP_STAB_50PPM),
 282    DEFINE_PROP_END_OF_LIST(),
 283};
 284
 285static void si57x_class_init(ObjectClass *klass, void *data)
 286{
 287    DeviceClass *dc = DEVICE_CLASS(klass);
 288    I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
 289
 290    k->init = si57x_init;
 291    k->event = si57x_event;
 292    k->recv = si57x_rx;
 293    k->send = si57x_tx;
 294    dc->props = si57x_properties;
 295    dc->reset = si57x_reset;
 296}
 297
 298static const TypeInfo si57x_info = {
 299    .name = TYPE_SI57X,
 300    .parent = TYPE_I2C_SLAVE,
 301    .instance_size = sizeof(Si57xState),
 302    .class_init = si57x_class_init,
 303};
 304
 305static void si57x_register_type(void)
 306{
 307    type_register_static(&si57x_info);
 308}
 309
 310type_init(si57x_register_type)
 311