From patchwork Fri Jan 18 06:32:09 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [13/18] hw: add QEMU model for Faraday multi-function SSP controller: SPI and I2S Date: Thu, 17 Jan 2013 20:32:09 -0000 From: Dante X-Patchwork-Id: 213495 Message-Id: <1358490729-18621-1-git-send-email-dantesu@faraday-tech.com> To: Cc: peter.maydell@linaro.org, paul@codesourcery.com, dantesu@faraday-tech.com Signed-off-by: Kuo-Jung Su --- hw/ftssp010.c | 526 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/ftssp010.h | 103 +++++++++++ 2 files changed, 629 insertions(+) create mode 100644 hw/ftssp010.c create mode 100644 hw/ftssp010.h diff --git a/hw/ftssp010.c b/hw/ftssp010.c new file mode 100644 index 0000000..61ce495 --- /dev/null +++ b/hw/ftssp010.c @@ -0,0 +1,526 @@ +/* + * QEMU model of the FTSSP010 Controller + * + * Copyright (C) 2012 Faraday Technology + * Copyright (C) 2012 Dante Su + * + * 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 "sysbus.h" +#include "sysemu/sysemu.h" +#include "fifo.h" +#include "i2c.h" +#include "ssi.h" +#include "ftssp010.h" + +#define CFG_FIFO_DEPTH 16 + +typedef struct { + SysBusDevice busdev; + MemoryRegion mmio; + + qemu_irq irq; + SSIBus *spi; + + uint8_t num_cs; + qemu_irq *cs_lines; + + Fifo8 rx_fifo; + Fifo8 tx_fifo; + + uint8_t tx_thres; + uint8_t rx_thres; + + int busy; + uint8_t bw; + + /* I2S */ + void *codec_i2c; + char *codec_name; + uint8_t codec_addr; + void *codec; + void (*codec_out)(void *, uint32_t); + uint32_t (*codec_in)(void *); + + /* DMA hardware handshake */ + qemu_irq req[2]; /* 0 - Tx, 1 - Rx */ + + /* HW register caches */ + + uint32_t cr0; + uint32_t cr1; + uint32_t cr2; + uint32_t icr; /* interrupt control register */ + uint32_t isr; /* interrupt status register */ + +} ftssp010_state; + +/* Update interrupts. */ +static void ftssp010_update(ftssp010_state *s) +{ + if (!(s->cr2 & CR2_SSPEN)) + return; + + /* tx fifo status update */ + if ((s->tx_fifo.num / (s->bw >> 3)) <= s->tx_thres) { + s->isr |= ISR_TFTHI; + if (s->icr & ICR_TFDMA) + qemu_set_irq(s->req[0], 1); + } else { + s->isr &= ~ISR_TFTHI; + } + + /* rx fifo status update */ + switch(s->cr0 & 0x00007000) { + case CR0_FFMT_SPI: + s->isr |= ISR_RFTHI; + if (s->icr & ICR_RFDMA) + qemu_set_irq(s->req[1], 1); + break; + default: + if ((s->rx_fifo.num / (s->bw >> 3)) >= s->rx_thres) { + s->isr |= ISR_RFTHI; + if (s->icr & ICR_RFDMA) + qemu_set_irq(s->req[1], 1); + } else { + s->isr &= ~ISR_RFTHI; + } + break; + } + + /* update the interrupt signal */ + if ((s->icr & s->isr) & 0x0f) + qemu_set_irq(s->irq, 1); + else + qemu_set_irq(s->irq, 0); +} + +static void ftssp010_handle_ack(void *opaque, int line, int level) +{ + ftssp010_state *s = (ftssp010_state *)opaque; + + switch(line) { + case 0: /* Tx */ + if (s->icr & ICR_TFDMA) { + if (level) + qemu_set_irq(s->req[0], 0); + if ((s->tx_fifo.num / (s->bw >> 3)) <= s->tx_thres) + qemu_set_irq(s->req[0], 1); + } + break; + case 1: /* Rx */ + if (s->icr & ICR_RFDMA) { + if (level) + qemu_set_irq(s->req[1], 0); + switch(s->cr0 & 0x00007000) { + case CR0_FFMT_SPI: + qemu_set_irq(s->req[1], 1); + break; + default: + if ((s->rx_fifo.num / (s->bw >> 3)) >= s->rx_thres) + qemu_set_irq(s->req[1], 1); + break; + } + } + break; + default: + break; + } +} + +static void ftssp010_i2s_data_req(void *opaque, int tx, int rx) +{ + int len; + uint32_t sample; + ftssp010_state *s = opaque; + + if (!(s->cr2 & CR2_SSPEN)) + return; + + if ((s->cr0 & 0x00007000) != CR0_FFMT_I2S) + return; + + s->busy = 1; + + if ((s->cr2 & (CR2_TXEN | CR2_TXDOE)) == (CR2_TXEN | CR2_TXDOE)) { + len = tx * (s->bw / 8); + while (!fifo8_is_empty(&s->tx_fifo) && len > 0) { + sample = fifo8_pop(&s->tx_fifo) << 0; + --len; + if (s->bw > 8) { + sample |= fifo8_pop(&s->tx_fifo) << 8; + --len; + } + if (s->bw > 16) { + sample |= fifo8_pop(&s->tx_fifo) << 16; + --len; + } + if (s->bw > 24) { + sample |= fifo8_pop(&s->tx_fifo) << 24; + --len; + } + s->codec_out(s->codec, sample); + } + + if (fifo8_is_empty(&s->tx_fifo) && len > 0) + s->isr |= ISR_TFURI; + } + + if (s->cr2 & CR2_RXEN) { + len = rx * (s->bw / 8); + while (!fifo8_is_full(&s->rx_fifo) && len > 0) { + sample = s->codec_in(s->codec); + fifo8_push(&s->rx_fifo, (sample >> 0) & 0xff); + --len; + if (s->bw > 8) { + fifo8_push(&s->rx_fifo, (sample >> 8) & 0xff); + --len; + } + if (s->bw > 16) { + fifo8_push(&s->rx_fifo, (sample >> 16) & 0xff); + --len; + } + if (s->bw > 24) { + fifo8_push(&s->rx_fifo, (sample >> 24) & 0xff); + --len; + } + } + + if (fifo8_is_full(&s->rx_fifo) && len > 0) + s->isr |= ISR_RFORI; + } + + s->busy = 0; + + ftssp010_update(s); +} + +static void ftssp010_spi_tx(ftssp010_state *s) +{ + if (!(s->cr2 & CR2_TXEN)) + return; + + s->busy = 1; + + if (fifo8_is_empty(&s->tx_fifo)) + s->isr |= ISR_TFURI; + + while(!fifo8_is_empty(&s->tx_fifo)) + ssi_transfer(s->spi, fifo8_pop(&s->tx_fifo)); + + s->busy = 0; +} + + +static uint64_t ftssp010_mem_read(void *opaque, hwaddr addr, unsigned int size) +{ + ftssp010_state *s = opaque; + uint32_t rc = 0; + + switch (addr) { + case REG_CR0: /* Control Register 0 */ + return s->cr0; + case REG_CR1: /* Control Register 1 */ + return s->cr1; + case REG_CR2: /* Control Register 2 */ + return s->cr2; + case REG_STR: /* Status Register */ + rc |= s->busy ? 0x04 : 0x00; + /* tx fifo status */ + rc |= STR_TFVE(s->tx_fifo.num / (s->bw >> 3)); + if (!fifo8_is_full(&s->tx_fifo)) + rc |= STR_TFNF; + /* rx fifo status */ + switch(s->cr0 & 0x00007000) { + case CR0_FFMT_SPI: + rc |= STR_RFF | STR_RFVE(CFG_FIFO_DEPTH); + break; + case CR0_FFMT_I2S: + rc |= STR_RFVE(s->rx_fifo.num / (s->bw >> 3)); + if (fifo8_is_full(&s->rx_fifo)) + rc |= STR_RFF; + break; + default: + break; + } + break; + case REG_ICR: /* Interrupt Control Register */ + return s->icr; + case REG_ISR: /* Interrupt Status Register */ + rc = s->isr; + s->isr &= 0xffffffec; /* Clear BIT0, BIT1, BIT4 */ + ftssp010_update(s); + break; + case REG_DTR: /* Data Register */ + if (!(s->cr2 & CR2_SSPEN)) + break; + if (!(s->cr2 & CR2_RXEN)) + break; + switch(s->cr0 & 0x00007000) { + case CR0_FFMT_SPI: + rc |= (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff) << 0; + if (s->bw > 8) + rc |= (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff) << 8; + if (s->bw > 16) + rc |= (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff) << 16; + if (s->bw > 24) + rc |= (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff) << 24; + break; + case CR0_FFMT_I2S: + if (!fifo8_is_empty(&s->rx_fifo)) + rc |= fifo8_pop(&s->rx_fifo); + if (!fifo8_is_empty(&s->rx_fifo) && s->bw > 8) + rc |= fifo8_pop(&s->rx_fifo) << 8; + if (!fifo8_is_empty(&s->rx_fifo) && s->bw > 16) + rc |= fifo8_pop(&s->rx_fifo) << 16; + if (!fifo8_is_empty(&s->rx_fifo) && s->bw > 24) + rc |= fifo8_pop(&s->rx_fifo) << 24; + break; + default: + break; + } + ftssp010_update(s); + break; + case REG_VER: + return 0x00011901; /* ver. 1.19.1 */ + case REG_FEA: + return 0x660f0f1f; /* SPI+I2S, FIFO=16 */ + default: + break; + } + + return rc; +} + +static void ftssp010_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size) +{ + ftssp010_state *s = opaque; + + switch (addr) { + case REG_CR0: /* Control Register 0 */ + s->cr0 = (uint32_t)val; + break; + case REG_CR1: /* Control Register 1 */ + s->cr1 = (uint32_t)val; + s->bw = ((s->cr1 >> 16) & 0x3f) + 1; + break; + case REG_CR2: /* Control Register 2 */ + s->cr2 = (uint32_t)val; + if (s->cr2 & CR2_SSPRST) { + fifo8_reset(&s->tx_fifo); + fifo8_reset(&s->rx_fifo); + s->busy = 0; + s->cr2 &= ~(CR2_SSPRST | CR2_TXFCLR | CR2_RXFCLR); + if (s->cr0 & CR0_FLASH) { + int cs = (s->cr2 >> 10) & 0x03; + qemu_set_irq(s->cs_lines[cs], 1); + s->cr2 |= CR2_FS; + }; + } + if (s->cr2 & CR2_TXFCLR) { + fifo8_reset(&s->tx_fifo); + s->cr2 &= ~CR2_TXFCLR; + } + if (s->cr2 & CR2_RXFCLR) { + fifo8_reset(&s->rx_fifo); + s->cr2 &= ~CR2_RXFCLR; + } + if (s->cr0 & CR0_FLASH) { + int cs = (s->cr2 >> 10) & 0x03; + qemu_set_irq(s->cs_lines[cs], (s->cr2 & CR2_FS) ? 1 : 0); + } + if (s->cr2 & CR2_SSPEN) { + switch(s->cr0 & 0x00007000) { + case CR0_FFMT_SPI: + ftssp010_spi_tx(s); + break; + default: + break; + } + } + ftssp010_update(s); + break; + case REG_ICR: /* Interrupt Control Register */ + s->icr = (uint32_t)val; + s->tx_thres = (s->icr >> 12) & 0x1f; + s->rx_thres = (s->icr >> 7) & 0x1f; + break; + case REG_DTR: /* Data Register */ + if (!(s->cr2 & CR2_SSPEN)) + break; + if (!fifo8_is_full(&s->tx_fifo)) + fifo8_push(&s->tx_fifo, (uint8_t)val); + if (!fifo8_is_full(&s->tx_fifo) && s->bw > 8) + fifo8_push(&s->tx_fifo, (uint8_t)(val >> 8)); + if (!fifo8_is_full(&s->tx_fifo) && s->bw > 16) + fifo8_push(&s->tx_fifo, (uint8_t)(val >> 16)); + if (!fifo8_is_full(&s->tx_fifo) && s->bw > 24) + fifo8_push(&s->tx_fifo, (uint8_t)(val >> 24)); + switch(s->cr0 & 0x00007000) { + case CR0_FFMT_SPI: + ftssp010_spi_tx(s); + break; + default: + break; + } + ftssp010_update(s); + break; + default: + break; + } +} + +static const MemoryRegionOps spi_ops = { + .read = ftssp010_mem_read, + .write = ftssp010_mem_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4 + } +}; + +static void ftssp010_reset(DeviceState *d) +{ + ftssp010_state *s = DO_UPCAST(ftssp010_state, busdev.qdev, d); + + s->busy = 0; + s->bw = 8; + + s->cr0 = 0; + s->cr1 = 0; + s->cr2 = 0; + s->icr = 0x00002200; + s->isr = 0x00000008; + + s->tx_thres = 0; + s->rx_thres = 0; + + fifo8_reset(&s->tx_fifo); + fifo8_reset(&s->rx_fifo); + + ftssp010_update(s); +} + +static int ftssp010_init(SysBusDevice *dev) +{ + int i; + ftssp010_state *s = FROM_SYSBUS(typeof(*s), dev); + + s->spi = ssi_create_bus(&dev->qdev, "spi"); + + fifo8_create(&s->tx_fifo, CFG_FIFO_DEPTH * 4); + fifo8_create(&s->rx_fifo, CFG_FIFO_DEPTH * 4); + + memory_region_init_io(&s->mmio, &spi_ops, s, "ftssp010", 0x1000); + sysbus_init_mmio(dev, &s->mmio); + sysbus_init_irq(dev, &s->irq); + + s->num_cs = 4; + s->cs_lines = g_new(qemu_irq, s->num_cs); + ssi_auto_connect_slaves(DEVICE(s), s->cs_lines, s->spi); + for (i = 0; i < s->num_cs; ++i) { + sysbus_init_irq(dev, &s->cs_lines[i]); + } + + /* DMA hardware handshake */ + qdev_init_gpio_in (&s->busdev.qdev, ftssp010_handle_ack, 2); + qdev_init_gpio_out(&s->busdev.qdev, s->req, 2); + + /* I2S */ + if (s->codec_i2c) { + DeviceState *wm; + const char *id = "wm8731"; + + if (s->codec_name) + id = s->codec_name; + + printf("[qemu] ftssp010: i2c bus registered, connecting codec[%s:0x%x]...\n", + id, s->codec_addr); + + /* Attach a codec to the bus */ + wm = i2c_create_slave(s->codec_i2c, id, s->codec_addr); + s->codec = wm; + if (!strcmp(id, "wm8731")) { + s->codec_out = wm8731_dac_dat; + s->codec_in = wm8731_adc_dat; + wm8731_data_req_set(wm, ftssp010_i2s_data_req, s); + } else if (!strcmp(id, "wm8750")) { + s->codec_out = wm8750_dac_dat; + s->codec_in = wm8750_adc_dat; + wm8750_data_req_set(wm, ftssp010_i2s_data_req, s); + } else { + printf("[qemu] ftssp010: unknown codec [%s] ?\n", id); + return -1; + } + } + + return 0; +} + +static Property ftssp010_properties[] = { + DEFINE_PROP_PTR ("codec_i2c", ftssp010_state, codec_i2c), + DEFINE_PROP_STRING("codec_name", ftssp010_state, codec_name), + DEFINE_PROP_UINT8 ("codec_addr", ftssp010_state, codec_addr, 0x1B), + DEFINE_PROP_END_OF_LIST(), +}; + +static const VMStateDescription vmstate_ftssp010 = { + .name = "ftssp010", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(cr0, ftssp010_state), + VMSTATE_UINT32(cr1, ftssp010_state), + VMSTATE_UINT32(cr2, ftssp010_state), + VMSTATE_UINT32(icr, ftssp010_state), + VMSTATE_UINT32(isr, ftssp010_state), + VMSTATE_FIFO8(tx_fifo, ftssp010_state), + VMSTATE_FIFO8(rx_fifo, ftssp010_state), + VMSTATE_END_OF_LIST() + } +}; + +static void ftssp010_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + DeviceClass *k = DEVICE_CLASS(klass); + + sdc->init = ftssp010_init; + k->vmsd = &vmstate_ftssp010; + k->props = ftssp010_properties; + k->reset = ftssp010_reset; + k->no_user= 1; +} + +static TypeInfo ftssp010_info = { + .name = "ftssp010", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(ftssp010_state), + .class_init = ftssp010_class_init, +}; + +static void ftssp010_register_types(void) +{ + type_register_static(&ftssp010_info); +} + +type_init(ftssp010_register_types) diff --git a/hw/ftssp010.h b/hw/ftssp010.h new file mode 100644 index 0000000..df1f27e --- /dev/null +++ b/hw/ftssp010.h @@ -0,0 +1,103 @@ +/* + * (C) Copyright 2010 + * Faraday Technology Inc. + * Dante Su + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef FTSSP010_H +#define FTSSP010_H + +/* FTSSP010: Registers */ +#define REG_CR0 0x00 +#define REG_CR1 0x04 +#define REG_CR2 0x08 +#define REG_STR 0x0C +#define REG_ICR 0X10 +#define REG_ISR 0x14 +#define REG_DTR 0x18 +#define REG_VER 0x60 +#define REG_FEA 0x64 + +/* Control register 0 */ + +#define CR0_FFMT_SSP (0 << 12) +#define CR0_FFMT_SPI (1 << 12) +#define CR0_FFMT_MICROWIRE (2 << 12) +#define CR0_FFMT_I2S (3 << 12) +#define CR0_FFMT_AC97 (4 << 12) +#define CR0_FLASH (1 << 11) +#define CR0_FSDIST(x) (((x) & 0x03) << 8) +#define CR0_LBM (1 << 7) /* Loopback mode */ +#define CR0_LSB (1 << 6) /* LSB first */ +#define CR0_FSPO (1 << 5) /* Frame sync atcive low */ +#define CR0_FSJUSTIFY (1 << 4) /* Data padding in front of serial data */ +#define CR0_OPM_SLAVE (0 << 2) +#define CR0_OPM_MASTER (3 << 2) +#define CR0_OPM_I2S_MSST (3 << 2) /* Master stereo mode */ +#define CR0_OPM_I2S_MSMO (2 << 2) /* Master mono mode */ +#define CR0_OPM_I2S_SLST (1 << 2) /* Slave stereo mode */ +#define CR0_OPM_I2S_SLMO (0 << 2) /* Slave mono mode */ +#define CR0_SCLKPO (1 << 1) /* SCLK Remain HIGH */ +#define CR0_SCLKPH (1 << 0) /* Half CLK cycle */ + +/* Control Register 1 */ + +#define CR1_PDL(x) (((x) & 0xff) << 24) /* padding data length */ +#define CR1_SDL(x) ((((x) - 1) & 0x1f) << 16) /* serial data length(actual data length-1) */ +#define CR1_CLKDIV(x) ((x) & 0xffff) /* clk divider */ + +/* Control Register 2 */ +#define CR2_FSOS(x) (((x) & 0x03) << 10) /* FS/CS Select */ +#define CR2_FS (1 << 9) /* FS/CS Signal Level */ +#define CR2_TXEN (1 << 8) /* Tx Enable */ +#define CR2_RXEN (1 << 7) /* Rx Enable */ +#define CR2_SSPRST (1 << 6) /* SSP reset */ +#define CR2_TXFCLR (1 << 3) /* TX FIFO Clear */ +#define CR2_RXFCLR (1 << 2) /* RX FIFO Clear */ +#define CR2_TXDOE (1 << 1) /* TX Data Output Enable */ +#define CR2_SSPEN (1 << 0) /* SSP Enable */ + +/* + * Status Register + */ +#define STR_TFVE(reg) (((reg) & 0x1F) << 12) +#define STR_RFVE(reg) (((reg) & 0x1F) << 4) +#define STR_BUSY (1 << 2) +#define STR_TFNF (1 << 1) /* Tx FIFO Not Full */ +#define STR_RFF (1 << 0) /* Rx FIFO Full */ + +/* Interrupr Control Register */ +#define ICR_TFTHOD(x) (((x) & 0x0f) << 12) /* TX FIFO Threshold */ +#define ICR_RFTHOD(x) (((x) & 0x0f) << 8) /* RX FIFO Threshold */ +#define ICR_TFDMA 0x20 /* TX DMA Enable */ +#define ICR_RFDMA 0x10 /* RX DMA Enable */ +#define ICR_TFTHI 0x08 /* TX FIFO Int Enable */ +#define ICR_RFTHI 0x04 /* RX FIFO Int Enable */ +#define ICR_TFURI 0x02 /* TX FIFO Underrun int enable */ +#define ICR_RFORI 0x01 /* RX FIFO Overrun int enable */ + +/* Interrupr Status Register */ +#define ISR_TFTHI 0x08 /* TX FIFO Int Enable */ +#define ISR_RFTHI 0x04 /* RX FIFO Int Enable */ +#define ISR_TFURI 0x02 /* TX FIFO Underrun int enable */ +#define ISR_RFORI 0x01 /* RX FIFO Overrun int enable */ + +#endif