qemu
1/*
2* CBUS three-pin bus and the Retu / Betty / Tahvo / Vilma / Avilma /
3* Hinku / Vinku / Ahne / Pihi chips used in various Nokia platforms.
4* Based on reverse-engineering of a linux driver.
5*
6* Copyright (C) 2008 Nokia Corporation
7* Written by Andrzej Zaborowski <andrew@openedhand.com>
8*
9* This program is free software; you can redistribute it and/or
10* modify it under the terms of the GNU General Public License as
11* published by the Free Software Foundation; either version 2 or
12* (at your option) version 3 of the License.
13*
14* This program is distributed in the hope that it will be useful,
15* but WITHOUT ANY WARRANTY; without even the implied warranty of
16* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17* GNU General Public License for more details.
18*
19* You should have received a copy of the GNU General Public License along
20* with this program; if not, see <http://www.gnu.org/licenses/>.
21*/
22
23#include "qemu/osdep.h"24#include "hw/hw.h"25#include "hw/irq.h"26#include "hw/misc/cbus.h"27#include "sysemu/runstate.h"28
29//#define DEBUG
30
31typedef struct {32void *opaque;33void (*io)(void *opaque, int rw, int reg, uint16_t *val);34int addr;35} CBusSlave;36
37typedef struct {38CBus cbus;39
40int sel;41int dat;42int clk;43int bit;44int dir;45uint16_t val;46qemu_irq dat_out;47
48int addr;49int reg;50int rw;51enum {52cbus_address,53cbus_value,54} cycle;55
56CBusSlave *slave[8];57} CBusPriv;58
59static void cbus_io(CBusPriv *s)60{
61if (s->slave[s->addr])62s->slave[s->addr]->io(s->slave[s->addr]->opaque,63s->rw, s->reg, &s->val);64else65hw_error("%s: bad slave address %i\n", __func__, s->addr);66}
67
68static void cbus_cycle(CBusPriv *s)69{
70switch (s->cycle) {71case cbus_address:72s->addr = (s->val >> 6) & 7;73s->rw = (s->val >> 5) & 1;74s->reg = (s->val >> 0) & 0x1f;75
76s->cycle = cbus_value;77s->bit = 15;78s->dir = !s->rw;79s->val = 0;80
81if (s->rw)82cbus_io(s);83break;84
85case cbus_value:86if (!s->rw)87cbus_io(s);88
89s->cycle = cbus_address;90s->bit = 8;91s->dir = 1;92s->val = 0;93break;94}95}
96
97static void cbus_clk(void *opaque, int line, int level)98{
99CBusPriv *s = (CBusPriv *) opaque;100
101if (!s->sel && level && !s->clk) {102if (s->dir)103s->val |= s->dat << (s->bit --);104else105qemu_set_irq(s->dat_out, (s->val >> (s->bit --)) & 1);106
107if (s->bit < 0)108cbus_cycle(s);109}110
111s->clk = level;112}
113
114static void cbus_dat(void *opaque, int line, int level)115{
116CBusPriv *s = (CBusPriv *) opaque;117
118s->dat = level;119}
120
121static void cbus_sel(void *opaque, int line, int level)122{
123CBusPriv *s = (CBusPriv *) opaque;124
125if (!level) {126s->dir = 1;127s->bit = 8;128s->val = 0;129}130
131s->sel = level;132}
133
134CBus *cbus_init(qemu_irq dat)135{
136CBusPriv *s = g_malloc0(sizeof(*s));137
138s->dat_out = dat;139s->cbus.clk = qemu_allocate_irq(cbus_clk, s, 0);140s->cbus.dat = qemu_allocate_irq(cbus_dat, s, 0);141s->cbus.sel = qemu_allocate_irq(cbus_sel, s, 0);142
143s->sel = 1;144s->clk = 0;145s->dat = 0;146
147return &s->cbus;148}
149
150void cbus_attach(CBus *bus, void *slave_opaque)151{
152CBusSlave *slave = (CBusSlave *) slave_opaque;153CBusPriv *s = (CBusPriv *) bus;154
155s->slave[slave->addr] = slave;156}
157
158/* Retu/Vilma */
159typedef struct {160uint16_t irqst;161uint16_t irqen;162uint16_t cc[2];163int channel;164uint16_t result[16];165uint16_t sample;166uint16_t status;167
168struct {169uint16_t cal;170} rtc;171
172int is_vilma;173qemu_irq irq;174CBusSlave cbus;175} CBusRetu;176
177static void retu_interrupt_update(CBusRetu *s)178{
179qemu_set_irq(s->irq, s->irqst & ~s->irqen);180}
181
182#define RETU_REG_ASICR 0x00 /* (RO) ASIC ID & revision */183#define RETU_REG_IDR 0x01 /* (T) Interrupt ID */184#define RETU_REG_IMR 0x02 /* (RW) Interrupt mask */185#define RETU_REG_RTCDSR 0x03 /* (RW) RTC seconds register */186#define RETU_REG_RTCHMR 0x04 /* (RO) RTC hours and minutes reg */187#define RETU_REG_RTCHMAR 0x05 /* (RW) RTC hours and minutes set reg */188#define RETU_REG_RTCCALR 0x06 /* (RW) RTC calibration register */189#define RETU_REG_ADCR 0x08 /* (RW) ADC result register */190#define RETU_REG_ADCSCR 0x09 /* (RW) ADC sample control register */191#define RETU_REG_AFCR 0x0a /* (RW) AFC register */192#define RETU_REG_ANTIFR 0x0b /* (RW) AntiF register */193#define RETU_REG_CALIBR 0x0c /* (RW) CalibR register*/194#define RETU_REG_CCR1 0x0d /* (RW) Common control register 1 */195#define RETU_REG_CCR2 0x0e /* (RW) Common control register 2 */196#define RETU_REG_RCTRL_CLR 0x0f /* (T) Regulator clear register */197#define RETU_REG_RCTRL_SET 0x10 /* (T) Regulator set register */198#define RETU_REG_TXCR 0x11 /* (RW) TxC register */199#define RETU_REG_STATUS 0x16 /* (RO) Status register */200#define RETU_REG_WATCHDOG 0x17 /* (RW) Watchdog register */201#define RETU_REG_AUDTXR 0x18 /* (RW) Audio Codec Tx register */202#define RETU_REG_AUDPAR 0x19 /* (RW) AudioPA register */203#define RETU_REG_AUDRXR1 0x1a /* (RW) Audio receive register 1 */204#define RETU_REG_AUDRXR2 0x1b /* (RW) Audio receive register 2 */205#define RETU_REG_SGR1 0x1c /* (RW) */206#define RETU_REG_SCR1 0x1d /* (RW) */207#define RETU_REG_SGR2 0x1e /* (RW) */208#define RETU_REG_SCR2 0x1f /* (RW) */209
210/* Retu Interrupt sources */
211enum {212retu_int_pwr = 0, /* Power button */213retu_int_char = 1, /* Charger */214retu_int_rtcs = 2, /* Seconds */215retu_int_rtcm = 3, /* Minutes */216retu_int_rtcd = 4, /* Days */217retu_int_rtca = 5, /* Alarm */218retu_int_hook = 6, /* Hook */219retu_int_head = 7, /* Headset */220retu_int_adcs = 8, /* ADC sample */221};222
223/* Retu ADC channel wiring */
224enum {225retu_adc_bsi = 1, /* BSI */226retu_adc_batt_temp = 2, /* Battery temperature */227retu_adc_chg_volt = 3, /* Charger voltage */228retu_adc_head_det = 4, /* Headset detection */229retu_adc_hook_det = 5, /* Hook detection */230retu_adc_rf_gp = 6, /* RF GP */231retu_adc_tx_det = 7, /* Wideband Tx detection */232retu_adc_batt_volt = 8, /* Battery voltage */233retu_adc_sens = 10, /* Light sensor */234retu_adc_sens_temp = 11, /* Light sensor temperature */235retu_adc_bbatt_volt = 12, /* Backup battery voltage */236retu_adc_self_temp = 13, /* RETU temperature */237};238
239static inline uint16_t retu_read(CBusRetu *s, int reg)240{
241#ifdef DEBUG242printf("RETU read at %02x\n", reg);243#endif244
245switch (reg) {246case RETU_REG_ASICR:247return 0x0215 | (s->is_vilma << 7);248
249case RETU_REG_IDR: /* TODO: Or is this ffs(s->irqst)? */250return s->irqst;251
252case RETU_REG_IMR:253return s->irqen;254
255case RETU_REG_RTCDSR:256case RETU_REG_RTCHMR:257case RETU_REG_RTCHMAR:258/* TODO */259return 0x0000;260
261case RETU_REG_RTCCALR:262return s->rtc.cal;263
264case RETU_REG_ADCR:265return (s->channel << 10) | s->result[s->channel];266case RETU_REG_ADCSCR:267return s->sample;268
269case RETU_REG_AFCR:270case RETU_REG_ANTIFR:271case RETU_REG_CALIBR:272/* TODO */273return 0x0000;274
275case RETU_REG_CCR1:276return s->cc[0];277case RETU_REG_CCR2:278return s->cc[1];279
280case RETU_REG_RCTRL_CLR:281case RETU_REG_RCTRL_SET:282case RETU_REG_TXCR:283/* TODO */284return 0x0000;285
286case RETU_REG_STATUS:287return s->status;288
289case RETU_REG_WATCHDOG:290case RETU_REG_AUDTXR:291case RETU_REG_AUDPAR:292case RETU_REG_AUDRXR1:293case RETU_REG_AUDRXR2:294case RETU_REG_SGR1:295case RETU_REG_SCR1:296case RETU_REG_SGR2:297case RETU_REG_SCR2:298/* TODO */299return 0x0000;300
301default:302hw_error("%s: bad register %02x\n", __func__, reg);303}304}
305
306static inline void retu_write(CBusRetu *s, int reg, uint16_t val)307{
308#ifdef DEBUG309printf("RETU write of %04x at %02x\n", val, reg);310#endif311
312switch (reg) {313case RETU_REG_IDR:314s->irqst ^= val;315retu_interrupt_update(s);316break;317
318case RETU_REG_IMR:319s->irqen = val;320retu_interrupt_update(s);321break;322
323case RETU_REG_RTCDSR:324case RETU_REG_RTCHMAR:325/* TODO */326break;327
328case RETU_REG_RTCCALR:329s->rtc.cal = val;330break;331
332case RETU_REG_ADCR:333s->channel = (val >> 10) & 0xf;334s->irqst |= 1 << retu_int_adcs;335retu_interrupt_update(s);336break;337case RETU_REG_ADCSCR:338s->sample &= ~val;339break;340
341case RETU_REG_AFCR:342case RETU_REG_ANTIFR:343case RETU_REG_CALIBR:344
345case RETU_REG_CCR1:346s->cc[0] = val;347break;348case RETU_REG_CCR2:349s->cc[1] = val;350break;351
352case RETU_REG_RCTRL_CLR:353case RETU_REG_RCTRL_SET:354/* TODO */355break;356
357case RETU_REG_WATCHDOG:358if (val == 0 && (s->cc[0] & 2))359qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);360break;361
362case RETU_REG_TXCR:363case RETU_REG_AUDTXR:364case RETU_REG_AUDPAR:365case RETU_REG_AUDRXR1:366case RETU_REG_AUDRXR2:367case RETU_REG_SGR1:368case RETU_REG_SCR1:369case RETU_REG_SGR2:370case RETU_REG_SCR2:371/* TODO */372break;373
374default:375hw_error("%s: bad register %02x\n", __func__, reg);376}377}
378
379static void retu_io(void *opaque, int rw, int reg, uint16_t *val)380{
381CBusRetu *s = (CBusRetu *) opaque;382
383if (rw)384*val = retu_read(s, reg);385else386retu_write(s, reg, *val);387}
388
389void *retu_init(qemu_irq irq, int vilma)390{
391CBusRetu *s = g_malloc0(sizeof(*s));392
393s->irq = irq;394s->irqen = 0xffff;395s->irqst = 0x0000;396s->status = 0x0020;397s->is_vilma = !!vilma;398s->rtc.cal = 0x01;399s->result[retu_adc_bsi] = 0x3c2;400s->result[retu_adc_batt_temp] = 0x0fc;401s->result[retu_adc_chg_volt] = 0x165;402s->result[retu_adc_head_det] = 123;403s->result[retu_adc_hook_det] = 1023;404s->result[retu_adc_rf_gp] = 0x11;405s->result[retu_adc_tx_det] = 0x11;406s->result[retu_adc_batt_volt] = 0x250;407s->result[retu_adc_sens] = 2;408s->result[retu_adc_sens_temp] = 0x11;409s->result[retu_adc_bbatt_volt] = 0x3d0;410s->result[retu_adc_self_temp] = 0x330;411
412s->cbus.opaque = s;413s->cbus.io = retu_io;414s->cbus.addr = 1;415
416return &s->cbus;417}
418
419void retu_key_event(void *retu, int state)420{
421CBusSlave *slave = (CBusSlave *) retu;422CBusRetu *s = (CBusRetu *) slave->opaque;423
424s->irqst |= 1 << retu_int_pwr;425retu_interrupt_update(s);426
427if (state)428s->status &= ~(1 << 5);429else430s->status |= 1 << 5;431}
432
433#if 0434static void retu_head_event(void *retu, int state)435{
436CBusSlave *slave = (CBusSlave *) retu;437CBusRetu *s = (CBusRetu *) slave->opaque;438
439if ((s->cc[0] & 0x500) == 0x500) { /* TODO: Which bits? */440/* TODO: reissue the interrupt every 100ms or so. */441s->irqst |= 1 << retu_int_head;442retu_interrupt_update(s);443}444
445if (state)446s->result[retu_adc_head_det] = 50;447else448s->result[retu_adc_head_det] = 123;449}
450
451static void retu_hook_event(void *retu, int state)452{
453CBusSlave *slave = (CBusSlave *) retu;454CBusRetu *s = (CBusRetu *) slave->opaque;455
456if ((s->cc[0] & 0x500) == 0x500) {457/* TODO: reissue the interrupt every 100ms or so. */458s->irqst |= 1 << retu_int_hook;459retu_interrupt_update(s);460}461
462if (state)463s->result[retu_adc_hook_det] = 50;464else465s->result[retu_adc_hook_det] = 123;466}
467#endif468
469/* Tahvo/Betty */
470typedef struct {471uint16_t irqst;472uint16_t irqen;473uint8_t charger;474uint8_t backlight;475uint16_t usbr;476uint16_t power;477
478int is_betty;479qemu_irq irq;480CBusSlave cbus;481} CBusTahvo;482
483static void tahvo_interrupt_update(CBusTahvo *s)484{
485qemu_set_irq(s->irq, s->irqst & ~s->irqen);486}
487
488#define TAHVO_REG_ASICR 0x00 /* (RO) ASIC ID & revision */489#define TAHVO_REG_IDR 0x01 /* (T) Interrupt ID */490#define TAHVO_REG_IDSR 0x02 /* (RO) Interrupt status */491#define TAHVO_REG_IMR 0x03 /* (RW) Interrupt mask */492#define TAHVO_REG_CHAPWMR 0x04 /* (RW) Charger PWM */493#define TAHVO_REG_LEDPWMR 0x05 /* (RW) LED PWM */494#define TAHVO_REG_USBR 0x06 /* (RW) USB control */495#define TAHVO_REG_RCR 0x07 /* (RW) Some kind of power management */496#define TAHVO_REG_CCR1 0x08 /* (RW) Common control register 1 */497#define TAHVO_REG_CCR2 0x09 /* (RW) Common control register 2 */498#define TAHVO_REG_TESTR1 0x0a /* (RW) Test register 1 */499#define TAHVO_REG_TESTR2 0x0b /* (RW) Test register 2 */500#define TAHVO_REG_NOPR 0x0c /* (RW) Number of periods */501#define TAHVO_REG_FRR 0x0d /* (RO) FR */502
503static inline uint16_t tahvo_read(CBusTahvo *s, int reg)504{
505#ifdef DEBUG506printf("TAHVO read at %02x\n", reg);507#endif508
509switch (reg) {510case TAHVO_REG_ASICR:511return 0x0021 | (s->is_betty ? 0x0b00 : 0x0300); /* 22 in N810 */512
513case TAHVO_REG_IDR:514case TAHVO_REG_IDSR: /* XXX: what does this do? */515return s->irqst;516
517case TAHVO_REG_IMR:518return s->irqen;519
520case TAHVO_REG_CHAPWMR:521return s->charger;522
523case TAHVO_REG_LEDPWMR:524return s->backlight;525
526case TAHVO_REG_USBR:527return s->usbr;528
529case TAHVO_REG_RCR:530return s->power;531
532case TAHVO_REG_CCR1:533case TAHVO_REG_CCR2:534case TAHVO_REG_TESTR1:535case TAHVO_REG_TESTR2:536case TAHVO_REG_NOPR:537case TAHVO_REG_FRR:538return 0x0000;539
540default:541hw_error("%s: bad register %02x\n", __func__, reg);542}543}
544
545static inline void tahvo_write(CBusTahvo *s, int reg, uint16_t val)546{
547#ifdef DEBUG548printf("TAHVO write of %04x at %02x\n", val, reg);549#endif550
551switch (reg) {552case TAHVO_REG_IDR:553s->irqst ^= val;554tahvo_interrupt_update(s);555break;556
557case TAHVO_REG_IMR:558s->irqen = val;559tahvo_interrupt_update(s);560break;561
562case TAHVO_REG_CHAPWMR:563s->charger = val;564break;565
566case TAHVO_REG_LEDPWMR:567if (s->backlight != (val & 0x7f)) {568s->backlight = val & 0x7f;569printf("%s: LCD backlight now at %i / 127\n",570__func__, s->backlight);571}572break;573
574case TAHVO_REG_USBR:575s->usbr = val;576break;577
578case TAHVO_REG_RCR:579s->power = val;580break;581
582case TAHVO_REG_CCR1:583case TAHVO_REG_CCR2:584case TAHVO_REG_TESTR1:585case TAHVO_REG_TESTR2:586case TAHVO_REG_NOPR:587case TAHVO_REG_FRR:588break;589
590default:591hw_error("%s: bad register %02x\n", __func__, reg);592}593}
594
595static void tahvo_io(void *opaque, int rw, int reg, uint16_t *val)596{
597CBusTahvo *s = (CBusTahvo *) opaque;598
599if (rw)600*val = tahvo_read(s, reg);601else602tahvo_write(s, reg, *val);603}
604
605void *tahvo_init(qemu_irq irq, int betty)606{
607CBusTahvo *s = g_malloc0(sizeof(*s));608
609s->irq = irq;610s->irqen = 0xffff;611s->irqst = 0x0000;612s->is_betty = !!betty;613
614s->cbus.opaque = s;615s->cbus.io = tahvo_io;616s->cbus.addr = 2;617
618return &s->cbus;619}
620