| 
									
										
										
										
											2007-09-16 21:08:06 +00:00
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |  * Arm PrimeCell PL181 MultiMedia Card Interface | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (c) 2007 CodeSourcery. | 
					
						
							|  |  |  |  * Written by Paul Brook | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2011-06-26 12:21:35 +10:00
										 |  |  |  * This code is licensed under the GPL. | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-26 18:17:05 +00:00
										 |  |  | #include "qemu/osdep.h"
 | 
					
						
							| 
									
										
										
										
											2014-10-07 13:59:13 +02:00
										 |  |  | #include "sysemu/block-backend.h"
 | 
					
						
							| 
									
										
										
										
											2012-12-17 18:20:04 +01:00
										 |  |  | #include "sysemu/blockdev.h"
 | 
					
						
							| 
									
										
										
										
											2013-02-04 15:40:22 +01:00
										 |  |  | #include "hw/sysbus.h"
 | 
					
						
							| 
									
										
										
										
											2015-10-08 18:51:01 +05:30
										 |  |  | #include "hw/sd/sd.h"
 | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | //#define DEBUG_PL181 1
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef DEBUG_PL181
 | 
					
						
							| 
									
										
										
										
											2009-05-13 17:53:17 +00:00
										 |  |  | #define DPRINTF(fmt, ...) \
 | 
					
						
							|  |  |  | do { printf("pl181: " fmt , ## __VA_ARGS__); } while (0) | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | #else
 | 
					
						
							| 
									
										
										
										
											2009-05-13 17:53:17 +00:00
										 |  |  | #define DPRINTF(fmt, ...) do {} while(0)
 | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define PL181_FIFO_LEN 16
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-27 13:23:41 +02:00
										 |  |  | #define TYPE_PL181 "pl181"
 | 
					
						
							|  |  |  | #define PL181(obj) OBJECT_CHECK(PL181State, (obj), TYPE_PL181)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-27 13:20:09 +02:00
										 |  |  | typedef struct PL181State { | 
					
						
							| 
									
										
										
										
											2013-07-27 13:23:41 +02:00
										 |  |  |     SysBusDevice parent_obj; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-10-11 13:54:48 +02:00
										 |  |  |     MemoryRegion iomem; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:54:11 +00:00
										 |  |  |     SDState *card; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |     uint32_t clock; | 
					
						
							|  |  |  |     uint32_t power; | 
					
						
							|  |  |  |     uint32_t cmdarg; | 
					
						
							|  |  |  |     uint32_t cmd; | 
					
						
							|  |  |  |     uint32_t datatimer; | 
					
						
							|  |  |  |     uint32_t datalength; | 
					
						
							|  |  |  |     uint32_t respcmd; | 
					
						
							|  |  |  |     uint32_t response[4]; | 
					
						
							|  |  |  |     uint32_t datactrl; | 
					
						
							|  |  |  |     uint32_t datacnt; | 
					
						
							|  |  |  |     uint32_t status; | 
					
						
							|  |  |  |     uint32_t mask[2]; | 
					
						
							| 
									
										
										
										
											2011-12-19 12:01:44 +00:00
										 |  |  |     int32_t fifo_pos; | 
					
						
							|  |  |  |     int32_t fifo_len; | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |     /* The linux 2.6.21 driver is buggy, and misbehaves if new data arrives
 | 
					
						
							| 
									
										
										
										
											2015-09-08 22:45:14 +01:00
										 |  |  |        while it is reading the FIFO.  We hack around this by deferring | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |        subsequent transfers until after the driver polls the status word. | 
					
						
							|  |  |  |        http://www.arm.linux.org.uk/developer/patches/viewpatch.php?id=4446/1
 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2011-12-19 12:01:44 +00:00
										 |  |  |     int32_t linux_hack; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |     uint32_t fifo[PL181_FIFO_LEN]; | 
					
						
							| 
									
										
										
										
											2007-04-07 18:14:41 +00:00
										 |  |  |     qemu_irq irq[2]; | 
					
						
							| 
									
										
										
										
											2011-02-21 20:57:49 +00:00
										 |  |  |     /* GPIO outputs for 'card is readonly' and 'card inserted' */ | 
					
						
							|  |  |  |     qemu_irq cardstatus[2]; | 
					
						
							| 
									
										
										
										
											2013-07-27 13:20:09 +02:00
										 |  |  | } PL181State; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-12-19 12:01:44 +00:00
										 |  |  | static const VMStateDescription vmstate_pl181 = { | 
					
						
							|  |  |  |     .name = "pl181", | 
					
						
							|  |  |  |     .version_id = 1, | 
					
						
							|  |  |  |     .minimum_version_id = 1, | 
					
						
							|  |  |  |     .fields = (VMStateField[]) { | 
					
						
							| 
									
										
										
										
											2013-07-27 13:20:09 +02:00
										 |  |  |         VMSTATE_UINT32(clock, PL181State), | 
					
						
							|  |  |  |         VMSTATE_UINT32(power, PL181State), | 
					
						
							|  |  |  |         VMSTATE_UINT32(cmdarg, PL181State), | 
					
						
							|  |  |  |         VMSTATE_UINT32(cmd, PL181State), | 
					
						
							|  |  |  |         VMSTATE_UINT32(datatimer, PL181State), | 
					
						
							|  |  |  |         VMSTATE_UINT32(datalength, PL181State), | 
					
						
							|  |  |  |         VMSTATE_UINT32(respcmd, PL181State), | 
					
						
							|  |  |  |         VMSTATE_UINT32_ARRAY(response, PL181State, 4), | 
					
						
							|  |  |  |         VMSTATE_UINT32(datactrl, PL181State), | 
					
						
							|  |  |  |         VMSTATE_UINT32(datacnt, PL181State), | 
					
						
							|  |  |  |         VMSTATE_UINT32(status, PL181State), | 
					
						
							|  |  |  |         VMSTATE_UINT32_ARRAY(mask, PL181State, 2), | 
					
						
							|  |  |  |         VMSTATE_INT32(fifo_pos, PL181State), | 
					
						
							|  |  |  |         VMSTATE_INT32(fifo_len, PL181State), | 
					
						
							|  |  |  |         VMSTATE_INT32(linux_hack, PL181State), | 
					
						
							|  |  |  |         VMSTATE_UINT32_ARRAY(fifo, PL181State, PL181_FIFO_LEN), | 
					
						
							| 
									
										
										
										
											2011-12-19 12:01:44 +00:00
										 |  |  |         VMSTATE_END_OF_LIST() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | #define PL181_CMD_INDEX     0x3f
 | 
					
						
							|  |  |  | #define PL181_CMD_RESPONSE  (1 << 6)
 | 
					
						
							|  |  |  | #define PL181_CMD_LONGRESP  (1 << 7)
 | 
					
						
							|  |  |  | #define PL181_CMD_INTERRUPT (1 << 8)
 | 
					
						
							|  |  |  | #define PL181_CMD_PENDING   (1 << 9)
 | 
					
						
							|  |  |  | #define PL181_CMD_ENABLE    (1 << 10)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define PL181_DATA_ENABLE             (1 << 0)
 | 
					
						
							|  |  |  | #define PL181_DATA_DIRECTION          (1 << 1)
 | 
					
						
							|  |  |  | #define PL181_DATA_MODE               (1 << 2)
 | 
					
						
							|  |  |  | #define PL181_DATA_DMAENABLE          (1 << 3)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define PL181_STATUS_CMDCRCFAIL       (1 << 0)
 | 
					
						
							|  |  |  | #define PL181_STATUS_DATACRCFAIL      (1 << 1)
 | 
					
						
							|  |  |  | #define PL181_STATUS_CMDTIMEOUT       (1 << 2)
 | 
					
						
							|  |  |  | #define PL181_STATUS_DATATIMEOUT      (1 << 3)
 | 
					
						
							|  |  |  | #define PL181_STATUS_TXUNDERRUN       (1 << 4)
 | 
					
						
							|  |  |  | #define PL181_STATUS_RXOVERRUN        (1 << 5)
 | 
					
						
							|  |  |  | #define PL181_STATUS_CMDRESPEND       (1 << 6)
 | 
					
						
							|  |  |  | #define PL181_STATUS_CMDSENT          (1 << 7)
 | 
					
						
							|  |  |  | #define PL181_STATUS_DATAEND          (1 << 8)
 | 
					
						
							|  |  |  | #define PL181_STATUS_DATABLOCKEND     (1 << 10)
 | 
					
						
							|  |  |  | #define PL181_STATUS_CMDACTIVE        (1 << 11)
 | 
					
						
							|  |  |  | #define PL181_STATUS_TXACTIVE         (1 << 12)
 | 
					
						
							|  |  |  | #define PL181_STATUS_RXACTIVE         (1 << 13)
 | 
					
						
							|  |  |  | #define PL181_STATUS_TXFIFOHALFEMPTY  (1 << 14)
 | 
					
						
							|  |  |  | #define PL181_STATUS_RXFIFOHALFFULL   (1 << 15)
 | 
					
						
							|  |  |  | #define PL181_STATUS_TXFIFOFULL       (1 << 16)
 | 
					
						
							|  |  |  | #define PL181_STATUS_RXFIFOFULL       (1 << 17)
 | 
					
						
							|  |  |  | #define PL181_STATUS_TXFIFOEMPTY      (1 << 18)
 | 
					
						
							|  |  |  | #define PL181_STATUS_RXFIFOEMPTY      (1 << 19)
 | 
					
						
							|  |  |  | #define PL181_STATUS_TXDATAAVLBL      (1 << 20)
 | 
					
						
							|  |  |  | #define PL181_STATUS_RXDATAAVLBL      (1 << 21)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define PL181_STATUS_TX_FIFO (PL181_STATUS_TXACTIVE \
 | 
					
						
							|  |  |  |                              |PL181_STATUS_TXFIFOHALFEMPTY \ | 
					
						
							|  |  |  |                              |PL181_STATUS_TXFIFOFULL \ | 
					
						
							|  |  |  |                              |PL181_STATUS_TXFIFOEMPTY \ | 
					
						
							|  |  |  |                              |PL181_STATUS_TXDATAAVLBL) | 
					
						
							|  |  |  | #define PL181_STATUS_RX_FIFO (PL181_STATUS_RXACTIVE \
 | 
					
						
							|  |  |  |                              |PL181_STATUS_RXFIFOHALFFULL \ | 
					
						
							|  |  |  |                              |PL181_STATUS_RXFIFOFULL \ | 
					
						
							|  |  |  |                              |PL181_STATUS_RXFIFOEMPTY \ | 
					
						
							|  |  |  |                              |PL181_STATUS_RXDATAAVLBL) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const unsigned char pl181_id[] = | 
					
						
							|  |  |  | { 0x81, 0x11, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-27 13:20:09 +02:00
										 |  |  | static void pl181_update(PL181State *s) | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | { | 
					
						
							|  |  |  |     int i; | 
					
						
							|  |  |  |     for (i = 0; i < 2; i++) { | 
					
						
							| 
									
										
										
										
											2007-04-07 18:14:41 +00:00
										 |  |  |         qemu_set_irq(s->irq[i], (s->status & s->mask[i]) != 0); | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-27 13:20:09 +02:00
										 |  |  | static void pl181_fifo_push(PL181State *s, uint32_t value) | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | { | 
					
						
							|  |  |  |     int n; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (s->fifo_len == PL181_FIFO_LEN) { | 
					
						
							|  |  |  |         fprintf(stderr, "pl181: FIFO overflow\n"); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     n = (s->fifo_pos + s->fifo_len) & (PL181_FIFO_LEN - 1); | 
					
						
							|  |  |  |     s->fifo_len++; | 
					
						
							|  |  |  |     s->fifo[n] = value; | 
					
						
							|  |  |  |     DPRINTF("FIFO push %08x\n", (int)value); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-27 13:20:09 +02:00
										 |  |  | static uint32_t pl181_fifo_pop(PL181State *s) | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | { | 
					
						
							|  |  |  |     uint32_t value; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (s->fifo_len == 0) { | 
					
						
							|  |  |  |         fprintf(stderr, "pl181: FIFO underflow\n"); | 
					
						
							|  |  |  |         return 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     value = s->fifo[s->fifo_pos]; | 
					
						
							|  |  |  |     s->fifo_len--; | 
					
						
							|  |  |  |     s->fifo_pos = (s->fifo_pos + 1) & (PL181_FIFO_LEN - 1); | 
					
						
							|  |  |  |     DPRINTF("FIFO pop %08x\n", (int)value); | 
					
						
							|  |  |  |     return value; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-27 13:20:09 +02:00
										 |  |  | static void pl181_send_command(PL181State *s) | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2009-05-10 01:44:56 +01:00
										 |  |  |     SDRequest request; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |     uint8_t response[16]; | 
					
						
							|  |  |  |     int rlen; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     request.cmd = s->cmd & PL181_CMD_INDEX; | 
					
						
							|  |  |  |     request.arg = s->cmdarg; | 
					
						
							|  |  |  |     DPRINTF("Command %d %08x\n", request.cmd, request.arg); | 
					
						
							|  |  |  |     rlen = sd_do_command(s->card, &request, response); | 
					
						
							|  |  |  |     if (rlen < 0) | 
					
						
							|  |  |  |         goto error; | 
					
						
							|  |  |  |     if (s->cmd & PL181_CMD_RESPONSE) { | 
					
						
							| 
									
										
										
										
											2013-06-27 15:03:51 +01:00
										 |  |  | #define RWORD(n) (((uint32_t)response[n] << 24) | (response[n + 1] << 16) \
 | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |                   | (response[n + 2] << 8) | response[n + 3]) | 
					
						
							|  |  |  |         if (rlen == 0 || (rlen == 4 && (s->cmd & PL181_CMD_LONGRESP))) | 
					
						
							|  |  |  |             goto error; | 
					
						
							|  |  |  |         if (rlen != 4 && rlen != 16) | 
					
						
							|  |  |  |             goto error; | 
					
						
							|  |  |  |         s->response[0] = RWORD(0); | 
					
						
							|  |  |  |         if (rlen == 4) { | 
					
						
							|  |  |  |             s->response[1] = s->response[2] = s->response[3] = 0; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             s->response[1] = RWORD(4); | 
					
						
							|  |  |  |             s->response[2] = RWORD(8); | 
					
						
							|  |  |  |             s->response[3] = RWORD(12) & ~1; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2007-07-11 22:48:58 +00:00
										 |  |  |         DPRINTF("Response received\n"); | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |         s->status |= PL181_STATUS_CMDRESPEND; | 
					
						
							|  |  |  | #undef RWORD
 | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         DPRINTF("Command sent\n"); | 
					
						
							|  |  |  |         s->status |= PL181_STATUS_CMDSENT; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | error: | 
					
						
							|  |  |  |     DPRINTF("Timeout\n"); | 
					
						
							|  |  |  |     s->status |= PL181_STATUS_CMDTIMEOUT; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-07-11 22:48:58 +00:00
										 |  |  | /* Transfer data between the card and the FIFO.  This is complicated by
 | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |    the FIFO holding 32-bit words and the card taking data in single byte | 
					
						
							|  |  |  |    chunks.  FIFO bytes are transferred in little-endian order.  */ | 
					
						
							| 
									
										
										
										
											2007-09-17 08:09:54 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-27 13:20:09 +02:00
										 |  |  | static void pl181_fifo_run(PL181State *s) | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | { | 
					
						
							|  |  |  |     uint32_t bits; | 
					
						
							| 
									
										
										
										
											2010-02-22 20:42:51 +00:00
										 |  |  |     uint32_t value = 0; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |     int n; | 
					
						
							|  |  |  |     int is_read; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     is_read = (s->datactrl & PL181_DATA_DIRECTION) != 0; | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |     if (s->datacnt != 0 && (!is_read || sd_data_ready(s->card)) | 
					
						
							|  |  |  |             && !s->linux_hack) { | 
					
						
							| 
									
										
										
										
											2010-02-22 15:33:59 +00:00
										 |  |  |         if (is_read) { | 
					
						
							|  |  |  |             n = 0; | 
					
						
							|  |  |  |             while (s->datacnt && s->fifo_len < PL181_FIFO_LEN) { | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |                 value |= (uint32_t)sd_read_data(s->card) << (n * 8); | 
					
						
							| 
									
										
										
										
											2010-02-22 15:33:59 +00:00
										 |  |  |                 s->datacnt--; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |                 n++; | 
					
						
							|  |  |  |                 if (n == 4) { | 
					
						
							|  |  |  |                     pl181_fifo_push(s, value); | 
					
						
							|  |  |  |                     n = 0; | 
					
						
							| 
									
										
										
										
											2010-02-22 15:33:59 +00:00
										 |  |  |                     value = 0; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2010-02-22 15:33:59 +00:00
										 |  |  |             } | 
					
						
							|  |  |  |             if (n != 0) { | 
					
						
							|  |  |  |                 pl181_fifo_push(s, value); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } else { /* write */ | 
					
						
							|  |  |  |             n = 0; | 
					
						
							|  |  |  |             while (s->datacnt > 0 && (s->fifo_len > 0 || n > 0)) { | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |                 if (n == 0) { | 
					
						
							|  |  |  |                     value = pl181_fifo_pop(s); | 
					
						
							|  |  |  |                     n = 4; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2010-02-22 15:33:59 +00:00
										 |  |  |                 n--; | 
					
						
							|  |  |  |                 s->datacnt--; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |                 sd_write_data(s->card, value & 0xff); | 
					
						
							|  |  |  |                 value >>= 8; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     s->status &= ~(PL181_STATUS_RX_FIFO | PL181_STATUS_TX_FIFO); | 
					
						
							|  |  |  |     if (s->datacnt == 0) { | 
					
						
							|  |  |  |         s->status |= PL181_STATUS_DATAEND; | 
					
						
							|  |  |  |         /* HACK: */ | 
					
						
							|  |  |  |         s->status |= PL181_STATUS_DATABLOCKEND; | 
					
						
							|  |  |  |         DPRINTF("Transfer Complete\n"); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |     if (s->datacnt == 0 && s->fifo_len == 0) { | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |         s->datactrl &= ~PL181_DATA_ENABLE; | 
					
						
							|  |  |  |         DPRINTF("Data engine idle\n"); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         /* Update FIFO bits.  */ | 
					
						
							|  |  |  |         bits = PL181_STATUS_TXACTIVE | PL181_STATUS_RXACTIVE; | 
					
						
							|  |  |  |         if (s->fifo_len == 0) { | 
					
						
							|  |  |  |             bits |= PL181_STATUS_TXFIFOEMPTY; | 
					
						
							|  |  |  |             bits |= PL181_STATUS_RXFIFOEMPTY; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             bits |= PL181_STATUS_TXDATAAVLBL; | 
					
						
							|  |  |  |             bits |= PL181_STATUS_RXDATAAVLBL; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (s->fifo_len == 16) { | 
					
						
							|  |  |  |             bits |= PL181_STATUS_TXFIFOFULL; | 
					
						
							|  |  |  |             bits |= PL181_STATUS_RXFIFOFULL; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (s->fifo_len <= 8) { | 
					
						
							|  |  |  |             bits |= PL181_STATUS_TXFIFOHALFEMPTY; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (s->fifo_len >= 8) { | 
					
						
							|  |  |  |             bits |= PL181_STATUS_RXFIFOHALFFULL; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (s->datactrl & PL181_DATA_DIRECTION) { | 
					
						
							|  |  |  |             bits &= PL181_STATUS_RX_FIFO; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             bits &= PL181_STATUS_TX_FIFO; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         s->status |= bits; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-23 12:30:10 +02:00
										 |  |  | static uint64_t pl181_read(void *opaque, hwaddr offset, | 
					
						
							| 
									
										
										
										
											2011-10-11 13:54:48 +02:00
										 |  |  |                            unsigned size) | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2013-07-27 13:20:09 +02:00
										 |  |  |     PL181State *s = (PL181State *)opaque; | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |     uint32_t tmp; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (offset >= 0xfe0 && offset < 0x1000) { | 
					
						
							|  |  |  |         return pl181_id[(offset - 0xfe0) >> 2]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     switch (offset) { | 
					
						
							|  |  |  |     case 0x00: /* Power */ | 
					
						
							|  |  |  |         return s->power; | 
					
						
							|  |  |  |     case 0x04: /* Clock */ | 
					
						
							|  |  |  |         return s->clock; | 
					
						
							|  |  |  |     case 0x08: /* Argument */ | 
					
						
							|  |  |  |         return s->cmdarg; | 
					
						
							|  |  |  |     case 0x0c: /* Command */ | 
					
						
							|  |  |  |         return s->cmd; | 
					
						
							|  |  |  |     case 0x10: /* RespCmd */ | 
					
						
							|  |  |  |         return s->respcmd; | 
					
						
							|  |  |  |     case 0x14: /* Response0 */ | 
					
						
							|  |  |  |         return s->response[0]; | 
					
						
							|  |  |  |     case 0x18: /* Response1 */ | 
					
						
							|  |  |  |         return s->response[1]; | 
					
						
							|  |  |  |     case 0x1c: /* Response2 */ | 
					
						
							|  |  |  |         return s->response[2]; | 
					
						
							|  |  |  |     case 0x20: /* Response3 */ | 
					
						
							|  |  |  |         return s->response[3]; | 
					
						
							|  |  |  |     case 0x24: /* DataTimer */ | 
					
						
							|  |  |  |         return s->datatimer; | 
					
						
							|  |  |  |     case 0x28: /* DataLength */ | 
					
						
							|  |  |  |         return s->datalength; | 
					
						
							|  |  |  |     case 0x2c: /* DataCtrl */ | 
					
						
							|  |  |  |         return s->datactrl; | 
					
						
							|  |  |  |     case 0x30: /* DataCnt */ | 
					
						
							|  |  |  |         return s->datacnt; | 
					
						
							|  |  |  |     case 0x34: /* Status */ | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |         tmp = s->status; | 
					
						
							|  |  |  |         if (s->linux_hack) { | 
					
						
							|  |  |  |             s->linux_hack = 0; | 
					
						
							|  |  |  |             pl181_fifo_run(s); | 
					
						
							|  |  |  |             pl181_update(s); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return tmp; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |     case 0x3c: /* Mask0 */ | 
					
						
							|  |  |  |         return s->mask[0]; | 
					
						
							|  |  |  |     case 0x40: /* Mask1 */ | 
					
						
							|  |  |  |         return s->mask[1]; | 
					
						
							|  |  |  |     case 0x48: /* FifoCnt */ | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |         /* The documentation is somewhat vague about exactly what FifoCnt
 | 
					
						
							|  |  |  |            does.  On real hardware it appears to be when decrememnted | 
					
						
							| 
									
										
										
										
											2011-11-29 16:52:39 +08:00
										 |  |  |            when a word is transferred between the FIFO and the serial | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |            data engine.  DataCnt is decremented after each byte is | 
					
						
							| 
									
										
										
										
											2011-11-29 16:52:39 +08:00
										 |  |  |            transferred between the serial engine and the card. | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |            We don't emulate this level of detail, so both can be the same.  */ | 
					
						
							|  |  |  |         tmp = (s->datacnt + 3) >> 2; | 
					
						
							|  |  |  |         if (s->linux_hack) { | 
					
						
							|  |  |  |             s->linux_hack = 0; | 
					
						
							|  |  |  |             pl181_fifo_run(s); | 
					
						
							|  |  |  |             pl181_update(s); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return tmp; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |     case 0x80: case 0x84: case 0x88: case 0x8c: /* FifoData */ | 
					
						
							|  |  |  |     case 0x90: case 0x94: case 0x98: case 0x9c: | 
					
						
							|  |  |  |     case 0xa0: case 0xa4: case 0xa8: case 0xac: | 
					
						
							|  |  |  |     case 0xb0: case 0xb4: case 0xb8: case 0xbc: | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |         if (s->fifo_len == 0) { | 
					
						
							| 
									
										
										
										
											2012-10-18 14:11:37 +01:00
										 |  |  |             qemu_log_mask(LOG_GUEST_ERROR, "pl181: Unexpected FIFO read\n"); | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |             return 0; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             uint32_t value; | 
					
						
							|  |  |  |             value = pl181_fifo_pop(s); | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |             s->linux_hack = 1; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |             pl181_fifo_run(s); | 
					
						
							|  |  |  |             pl181_update(s); | 
					
						
							|  |  |  |             return value; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     default: | 
					
						
							| 
									
										
										
										
											2012-10-18 14:11:37 +01:00
										 |  |  |         qemu_log_mask(LOG_GUEST_ERROR, | 
					
						
							|  |  |  |                       "pl181_read: Bad offset %x\n", (int)offset); | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |         return 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-23 12:30:10 +02:00
										 |  |  | static void pl181_write(void *opaque, hwaddr offset, | 
					
						
							| 
									
										
										
										
											2011-10-11 13:54:48 +02:00
										 |  |  |                         uint64_t value, unsigned size) | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2013-07-27 13:20:09 +02:00
										 |  |  |     PL181State *s = (PL181State *)opaque; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     switch (offset) { | 
					
						
							|  |  |  |     case 0x00: /* Power */ | 
					
						
							|  |  |  |         s->power = value & 0xff; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x04: /* Clock */ | 
					
						
							|  |  |  |         s->clock = value & 0xff; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x08: /* Argument */ | 
					
						
							|  |  |  |         s->cmdarg = value; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x0c: /* Command */ | 
					
						
							|  |  |  |         s->cmd = value; | 
					
						
							|  |  |  |         if (s->cmd & PL181_CMD_ENABLE) { | 
					
						
							|  |  |  |             if (s->cmd & PL181_CMD_INTERRUPT) { | 
					
						
							| 
									
										
										
										
											2012-10-18 14:11:37 +01:00
										 |  |  |                 qemu_log_mask(LOG_UNIMP, | 
					
						
							|  |  |  |                               "pl181: Interrupt mode not implemented\n"); | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |             } if (s->cmd & PL181_CMD_PENDING) { | 
					
						
							| 
									
										
										
										
											2012-10-18 14:11:37 +01:00
										 |  |  |                 qemu_log_mask(LOG_UNIMP, | 
					
						
							|  |  |  |                               "pl181: Pending commands not implemented\n"); | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |             } else { | 
					
						
							|  |  |  |                 pl181_send_command(s); | 
					
						
							|  |  |  |                 pl181_fifo_run(s); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             /* The command has completed one way or the other.  */ | 
					
						
							|  |  |  |             s->cmd &= ~PL181_CMD_ENABLE; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x24: /* DataTimer */ | 
					
						
							|  |  |  |         s->datatimer = value; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x28: /* DataLength */ | 
					
						
							|  |  |  |         s->datalength = value & 0xffff; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x2c: /* DataCtrl */ | 
					
						
							|  |  |  |         s->datactrl = value & 0xff; | 
					
						
							|  |  |  |         if (value & PL181_DATA_ENABLE) { | 
					
						
							|  |  |  |             s->datacnt = s->datalength; | 
					
						
							|  |  |  |             pl181_fifo_run(s); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x38: /* Clear */ | 
					
						
							|  |  |  |         s->status &= ~(value & 0x7ff); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x3c: /* Mask0 */ | 
					
						
							|  |  |  |         s->mask[0] = value; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x40: /* Mask1 */ | 
					
						
							|  |  |  |         s->mask[1] = value; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x80: case 0x84: case 0x88: case 0x8c: /* FifoData */ | 
					
						
							|  |  |  |     case 0x90: case 0x94: case 0x98: case 0x9c: | 
					
						
							|  |  |  |     case 0xa0: case 0xa4: case 0xa8: case 0xac: | 
					
						
							|  |  |  |     case 0xb0: case 0xb4: case 0xb8: case 0xbc: | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |         if (s->datacnt == 0) { | 
					
						
							| 
									
										
										
										
											2012-10-18 14:11:37 +01:00
										 |  |  |             qemu_log_mask(LOG_GUEST_ERROR, "pl181: Unexpected FIFO write\n"); | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |         } else { | 
					
						
							|  |  |  |             pl181_fifo_push(s, value); | 
					
						
							|  |  |  |             pl181_fifo_run(s); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     default: | 
					
						
							| 
									
										
										
										
											2012-10-18 14:11:37 +01:00
										 |  |  |         qemu_log_mask(LOG_GUEST_ERROR, | 
					
						
							|  |  |  |                       "pl181_write: Bad offset %x\n", (int)offset); | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |     } | 
					
						
							|  |  |  |     pl181_update(s); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-10-11 13:54:48 +02:00
										 |  |  | static const MemoryRegionOps pl181_ops = { | 
					
						
							|  |  |  |     .read = pl181_read, | 
					
						
							|  |  |  |     .write = pl181_write, | 
					
						
							|  |  |  |     .endianness = DEVICE_NATIVE_ENDIAN, | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-12-19 12:01:44 +00:00
										 |  |  | static void pl181_reset(DeviceState *d) | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2013-07-27 13:23:41 +02:00
										 |  |  |     PL181State *s = PL181(d); | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     s->power = 0; | 
					
						
							|  |  |  |     s->cmdarg = 0; | 
					
						
							|  |  |  |     s->cmd = 0; | 
					
						
							|  |  |  |     s->datatimer = 0; | 
					
						
							|  |  |  |     s->datalength = 0; | 
					
						
							|  |  |  |     s->respcmd = 0; | 
					
						
							|  |  |  |     s->response[0] = 0; | 
					
						
							|  |  |  |     s->response[1] = 0; | 
					
						
							|  |  |  |     s->response[2] = 0; | 
					
						
							|  |  |  |     s->response[3] = 0; | 
					
						
							|  |  |  |     s->datatimer = 0; | 
					
						
							|  |  |  |     s->datalength = 0; | 
					
						
							|  |  |  |     s->datactrl = 0; | 
					
						
							|  |  |  |     s->datacnt = 0; | 
					
						
							|  |  |  |     s->status = 0; | 
					
						
							| 
									
										
										
										
											2007-06-09 14:07:54 +00:00
										 |  |  |     s->linux_hack = 0; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  |     s->mask[0] = 0; | 
					
						
							|  |  |  |     s->mask[1] = 0; | 
					
						
							| 
									
										
										
										
											2011-02-21 20:57:49 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /* We can assume our GPIO outputs have been wired up now */ | 
					
						
							|  |  |  |     sd_set_cb(s->card, s->cardstatus[0], s->cardstatus[1]); | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-27 13:23:41 +02:00
										 |  |  | static int pl181_init(SysBusDevice *sbd) | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2013-07-27 13:23:41 +02:00
										 |  |  |     DeviceState *dev = DEVICE(sbd); | 
					
						
							|  |  |  |     PL181State *s = PL181(dev); | 
					
						
							| 
									
										
										
										
											2011-01-28 11:21:37 +01:00
										 |  |  |     DriveInfo *dinfo; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-06-06 21:25:08 -04:00
										 |  |  |     memory_region_init_io(&s->iomem, OBJECT(s), &pl181_ops, s, "pl181", 0x1000); | 
					
						
							| 
									
										
										
										
											2013-07-27 13:23:41 +02:00
										 |  |  |     sysbus_init_mmio(sbd, &s->iomem); | 
					
						
							|  |  |  |     sysbus_init_irq(sbd, &s->irq[0]); | 
					
						
							|  |  |  |     sysbus_init_irq(sbd, &s->irq[1]); | 
					
						
							|  |  |  |     qdev_init_gpio_out(dev, s->cardstatus, 2); | 
					
						
							| 
									
										
										
										
											2015-03-23 19:03:13 +01:00
										 |  |  |     /* FIXME use a qdev drive property instead of drive_get_next() */ | 
					
						
							| 
									
										
										
										
											2011-01-28 11:21:37 +01:00
										 |  |  |     dinfo = drive_get_next(IF_SD); | 
					
						
							| 
									
										
										
										
											2014-10-07 13:59:18 +02:00
										 |  |  |     s->card = sd_init(dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, false); | 
					
						
							| 
									
										
										
										
											2013-09-13 15:51:47 +02:00
										 |  |  |     if (s->card == NULL) { | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-08-14 10:36:05 +02:00
										 |  |  |     return 0; | 
					
						
							| 
									
										
										
										
											2007-04-06 16:49:48 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2009-05-14 22:35:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-01-24 13:12:29 -06:00
										 |  |  | static void pl181_class_init(ObjectClass *klass, void *data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); | 
					
						
							| 
									
										
										
										
											2011-12-07 21:34:16 -06:00
										 |  |  |     DeviceClass *k = DEVICE_CLASS(klass); | 
					
						
							| 
									
										
										
										
											2012-01-24 13:12:29 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     sdc->init = pl181_init; | 
					
						
							| 
									
										
										
										
											2011-12-07 21:34:16 -06:00
										 |  |  |     k->vmsd = &vmstate_pl181; | 
					
						
							|  |  |  |     k->reset = pl181_reset; | 
					
						
							| 
									
										
											  
											
												sysbus: Make devices picking up backends unavailable with -device
Device models aren't supposed to go on fishing expeditions for
backends.  They should expose suitable properties for the user to set.
For onboard devices, board code sets them.
A number of sysbus devices pick up block backends in their init() /
instance_init() methods with drive_get_next() instead: sl-nand,
milkymist-memcard, pl181, generic-sdhci.
Likewise, a number of sysbus devices pick up character backends in
their init() / realize() methods with qemu_char_get_next_serial():
cadence_uart, digic-uart, etraxfs,serial, lm32-juart, lm32-uart,
milkymist-uart, pl011, stm32f2xx-usart, xlnx.xps-uartlite.
All these mistakes are already marked FIXME.  See the commit that
added these FIXMEs for a more detailed explanation of what's wrong.
Fortunately, only machines ppce500 and pseries-* support -device with
sysbus devices, and none of the devices above is supported with these
machines.
Set cannot_instantiate_with_device_add_yet to preserve our luck.
Cc: Andrzej Zaborowski <balrogg@gmail.com>
Cc: Peter Crosthwaite <peter.crosthwaite@xilinx.com>
Cc: Antony Pavlov <antonynpavlov@gmail.com>
Cc: "Edgar E. Iglesias" <edgar.iglesias@gmail.com>
Cc: Michael Walle <michael@walle.cc>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
											
										 
											2015-03-23 19:34:40 +01:00
										 |  |  |     /* Reason: init() method uses drive_get_next() */ | 
					
						
							|  |  |  |     k->cannot_instantiate_with_device_add_yet = true; | 
					
						
							| 
									
										
										
										
											2012-01-24 13:12:29 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-01-10 16:19:07 +01:00
										 |  |  | static const TypeInfo pl181_info = { | 
					
						
							| 
									
										
										
										
											2013-07-27 13:23:41 +02:00
										 |  |  |     .name          = TYPE_PL181, | 
					
						
							| 
									
										
										
										
											2011-12-07 21:34:16 -06:00
										 |  |  |     .parent        = TYPE_SYS_BUS_DEVICE, | 
					
						
							| 
									
										
										
										
											2013-07-27 13:20:09 +02:00
										 |  |  |     .instance_size = sizeof(PL181State), | 
					
						
							| 
									
										
										
										
											2011-12-07 21:34:16 -06:00
										 |  |  |     .class_init    = pl181_class_init, | 
					
						
							| 
									
										
										
										
											2011-12-19 12:01:44 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-09 15:20:55 +01:00
										 |  |  | static void pl181_register_types(void) | 
					
						
							| 
									
										
										
										
											2009-05-14 22:35:07 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2011-12-07 21:34:16 -06:00
										 |  |  |     type_register_static(&pl181_info); | 
					
						
							| 
									
										
										
										
											2009-05-14 22:35:07 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-09 15:20:55 +01:00
										 |  |  | type_init(pl181_register_types) |