Files
qemu-ios/hw/arm/ipod_touch_sdio.c
T

253 lines
8.4 KiB
C

#include "hw/arm/ipod_touch_sdio.h"
static void trigger_irq(void *opaque)
{
IPodTouchSDIOState *s = (IPodTouchSDIOState *)opaque;
s->irq_reg = 0x2;
qemu_irq_raise(s->irq);
}
void sdio_exec_cmd(IPodTouchSDIOState *s)
{
uint32_t cmd_type = s->cmd & 0x3f;
uint32_t addr = (s->arg >> 9) & 0x1ffff;
uint32_t func = (s->arg >> 28) & 0x7;
printf("SDIO CMD: %d, ADDR: %d, FUNC: %d\n", cmd_type, addr, func);
if(cmd_type == 0x3) {
// RCA request - ignore
}
else if(cmd_type == 0x5) {
if(addr == 0) {
// reading slot 0 - make sure there is a device here
s->resp0 = (1 << 31) /* indicate ready */ | (BCM4325_FUNCTIONS << CMD5_FUNC_OFFSET) /* number of functions */;
}
}
else if(cmd_type == 0x7) {
// select card - ignore
}
else if(cmd_type == 0x34) {
// CMD52 - read/write from a register
bool is_write = (s->arg >> 31) != 0;
if(is_write) {
uint8_t data = s->arg & 0xFF;
s->registers[addr] = data;
if(addr == 0x2) { s->registers[0x3] = data; } // if we write to register 2, we also write the same result to register 3 (this is the enabled functions register)
printf("SDIO: Executing cmd52 by writing 0x%02x to register 0x%02x\n", data, addr);
} else {
if(addr == 0x1000e) {
// misc register
s->resp0 = (1 << 6) /* enable ALP clock */ | (1 << 7); /* enable HT clock */
}
else if(addr == 0x2020) {
// some indication that packets are ready??
s->resp0 = (1 << 6);
}
else {
printf("SDIO: Executing cmd52 by reading from 0x%02x (value: 0x%02x)\n", addr, s->registers[addr]);
s->resp0 = s->registers[addr];
}
}
}
else if(cmd_type == 0x35) {
// CMD53 - block transfer
addr = addr & 0x7fff;
bool is_write = (s->arg >> 31) != 0;
printf("SDIO: Executing cmd53 with block size %d and %d blocks (reg address: 0x%08x, destination address: 0x%08x, write? %d)\n", s->blklen, s->numblk, addr, s->baddr, is_write);
if(is_write) {
if(func == 0x1) {
cpu_physical_memory_read(s->baddr, &s->registers[addr], s->blklen * s->numblk);
}
else if(func == 0x2) {
// this is a BCM4325 command - add a frame to the queue and schedule the IRQ request to indicate that the command has been completed
BCM4325FrameHeaderPacket *frame_header = calloc(sizeof(BCM4325FrameHeaderPacket), sizeof(uint8_t *));
uint16_t length = 14;
frame_header->frame_length = length;
frame_header->checksum = length ^ 0xffff;
g_queue_push_tail(s->rx_fifo, frame_header);
timer_mod(s->irq_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + NANOSECONDS_PER_SECOND / 100);
}
} else {
if(func == 0x1) {
if(addr == 0x0) {
// chip ID register
uint32_t chipid[] = { 0x5 << 0x10 };
cpu_physical_memory_write(s->baddr, &chipid, 0x4);
}
}
else if(func == 0x2) {
// we're reading a frame
BCM4325FrameHeaderPacket *frame_header = (BCM4325FrameHeaderPacket *) g_queue_pop_head(s->rx_fifo);
if(!frame_header) {
// create an empty frame
frame_header = calloc(sizeof(BCM4325FrameHeaderPacket), sizeof(uint8_t *));
}
cpu_physical_memory_write(s->baddr, frame_header, sizeof(BCM4325FrameHeaderPacket));
}
}
// toggle IRQ register
s->irq_reg = 0x1;
qemu_irq_raise(s->irq);
//printf("Raised IRQ\n");
}
else {
hw_error("Unknown SDIO command %d", cmd_type);
}
}
static void ipod_touch_sdio_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
{
printf("%s: writing 0x%08x to 0x%08x\n", __func__, value, addr);
IPodTouchSDIOState *s = (struct IPodTouchSDIOState *) opaque;
switch(addr) {
case SDIO_CMD:
s->cmd = value;
if(value & (1 << 31)) { // execute bit is set
sdio_exec_cmd(s);
}
break;
case SDIO_ARGU:
s->arg = value;
break;
case SDIO_STATE:
s->state = value;
break;
case SDIO_STAC:
s->stac = value;
break;
case SDIO_CSR:
s->csr = value;
break;
case SDIO_IRQ:
qemu_irq_lower(s->irq);
break;
case SDIO_IRQMASK:
s->irq_mask = value;
break;
case SDIO_BADDR:
s->baddr = value;
break;
case SDIO_BLKLEN:
s->blklen = value;
break;
case SDIO_NUMBLK:
s->numblk = value;
break;
default:
break;
}
}
static uint64_t ipod_touch_sdio_read(void *opaque, hwaddr addr, unsigned size)
{
printf("%s: offset = 0x%08x\n", __func__, addr);
IPodTouchSDIOState *s = (struct IPodTouchSDIOState *) opaque;
switch (addr) {
case SDIO_CMD:
return s->cmd;
case SDIO_ARGU:
return s->arg;
case SDIO_STATE:
return s->state;
case SDIO_STAC:
return s->stac;
case SDIO_DSTA:
return (1 << 0) | (1 << 4) ; // 0x1 indicates that the SDIO is ready for a CMD, (1 << 4) that the command is complete
case SDIO_RESP0:
return s->resp0;
case SDIO_RESP1:
return s->resp1;
case SDIO_RESP2:
return s->resp2;
case SDIO_RESP3:
return s->resp3;
case SDIO_CSR:
return s->csr;
case SDIO_IRQ:
return s->irq_reg;
case SDIO_IRQMASK:
return s->irq_mask;
case SDIO_BADDR:
return s->baddr;
case SDIO_BLKLEN:
return s->blklen;
case SDIO_NUMBLK:
return s->numblk;
default:
break;
}
return 0;
}
static const MemoryRegionOps ipod_touch_sdio_ops = {
.read = ipod_touch_sdio_read,
.write = ipod_touch_sdio_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void ipod_touch_sdio_init(Object *obj)
{
DeviceState *dev = DEVICE(obj);
IPodTouchSDIOState *s = IPOD_TOUCH_SDIO(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
s->registers[0x9] = CIS_OFFSET; // registers 0x9 - 0xB contain the relative address offset, which we set to 0xC8 (200)
s->registers[19] = 0x1; // enable support for high speed mode
// set the vendor information
s->registers[CIS_OFFSET] = CIS_MANUFACTURER_ID;
s->registers[CIS_OFFSET + 1] = 0x4;
s->registers[CIS_OFFSET + 2] = BCM4325_MANUFACTURER & 0xFF;
s->registers[CIS_OFFSET + 3] = (BCM4325_MANUFACTURER >> 8) & 0xFF;
s->registers[CIS_OFFSET + 4] = BCM4325_PRODUCT_ID & 0xFF;
s->registers[CIS_OFFSET + 5] = (BCM4325_PRODUCT_ID >> 8) & 0xFF;
// set the MAC address (00:23:32:6E:AA:10)
s->registers[CIS_OFFSET + 6] = CIS_FUNCTION_EXTENSION;
s->registers[CIS_OFFSET + 8] = 0x4; // unknown
s->registers[CIS_OFFSET + 9] = 0x6; // the length of the MAC address
s->registers[CIS_OFFSET + 10] = 0x0;
s->registers[CIS_OFFSET + 11] = 0x23;
s->registers[CIS_OFFSET + 12] = 0x32;
s->registers[CIS_OFFSET + 13] = 0x6E;
s->registers[CIS_OFFSET + 14] = 0xAA;
s->registers[CIS_OFFSET + 15] = 0x10;
memory_region_init_io(&s->iomem, obj, &ipod_touch_sdio_ops, s, TYPE_IPOD_TOUCH_SDIO, 4096);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
s->irq_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, trigger_irq, s);
s->rx_fifo = g_queue_new();
}
static void ipod_touch_sdio_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
}
static const TypeInfo ipod_touch_sdio_type_info = {
.name = TYPE_IPOD_TOUCH_SDIO,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(IPodTouchSDIOState),
.instance_init = ipod_touch_sdio_init,
.class_init = ipod_touch_sdio_class_init,
};
static void ipod_touch_sdio_register_types(void)
{
type_register_static(&ipod_touch_sdio_type_info);
}
type_init(ipod_touch_sdio_register_types)