mirror of
				https://git.proxmox.com/git/qemu
				synced 2025-10-27 10:00:53 +00:00 
			
		
		
		
	 7267c0947d
			
		
	
	
		7267c0947d
		
	
	
	
	
		
			
			qemu_malloc/qemu_free no longer exist after this commit. Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
		
			
				
	
	
		
			347 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			347 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * TI OMAP processor's Multichannel SPI emulation.
 | |
|  *
 | |
|  * Copyright (C) 2007-2009 Nokia Corporation
 | |
|  *
 | |
|  * Original code for OMAP2 by Andrzej Zaborowski <andrew@openedhand.com>
 | |
|  *
 | |
|  * 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 or
 | |
|  * (at your option) any later version of the License.
 | |
|  *
 | |
|  * 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.,
 | |
|  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | |
|  */
 | |
| #include "hw.h"
 | |
| #include "omap.h"
 | |
| 
 | |
| /* Multichannel SPI */
 | |
| struct omap_mcspi_s {
 | |
|     qemu_irq irq;
 | |
|     int chnum;
 | |
| 
 | |
|     uint32_t sysconfig;
 | |
|     uint32_t systest;
 | |
|     uint32_t irqst;
 | |
|     uint32_t irqen;
 | |
|     uint32_t wken;
 | |
|     uint32_t control;
 | |
| 
 | |
|     struct omap_mcspi_ch_s {
 | |
|         qemu_irq txdrq;
 | |
|         qemu_irq rxdrq;
 | |
|         uint32_t (*txrx)(void *opaque, uint32_t, int);
 | |
|         void *opaque;
 | |
| 
 | |
|         uint32_t tx;
 | |
|         uint32_t rx;
 | |
| 
 | |
|         uint32_t config;
 | |
|         uint32_t status;
 | |
|         uint32_t control;
 | |
|     } ch[4];
 | |
| };
 | |
| 
 | |
| static inline void omap_mcspi_interrupt_update(struct omap_mcspi_s *s)
 | |
| {
 | |
|     qemu_set_irq(s->irq, s->irqst & s->irqen);
 | |
| }
 | |
| 
 | |
| static inline void omap_mcspi_dmarequest_update(struct omap_mcspi_ch_s *ch)
 | |
| {
 | |
|     qemu_set_irq(ch->txdrq,
 | |
|                     (ch->control & 1) &&		/* EN */
 | |
|                     (ch->config & (1 << 14)) &&		/* DMAW */
 | |
|                     (ch->status & (1 << 1)) &&		/* TXS */
 | |
|                     ((ch->config >> 12) & 3) != 1);	/* TRM */
 | |
|     qemu_set_irq(ch->rxdrq,
 | |
|                     (ch->control & 1) &&		/* EN */
 | |
|                     (ch->config & (1 << 15)) &&		/* DMAW */
 | |
|                     (ch->status & (1 << 0)) &&		/* RXS */
 | |
|                     ((ch->config >> 12) & 3) != 2);	/* TRM */
 | |
| }
 | |
| 
 | |
| static void omap_mcspi_transfer_run(struct omap_mcspi_s *s, int chnum)
 | |
| {
 | |
|     struct omap_mcspi_ch_s *ch = s->ch + chnum;
 | |
| 
 | |
|     if (!(ch->control & 1))				/* EN */
 | |
|         return;
 | |
|     if ((ch->status & (1 << 0)) &&			/* RXS */
 | |
|                     ((ch->config >> 12) & 3) != 2 &&	/* TRM */
 | |
|                     !(ch->config & (1 << 19)))		/* TURBO */
 | |
|         goto intr_update;
 | |
|     if ((ch->status & (1 << 1)) &&			/* TXS */
 | |
|                     ((ch->config >> 12) & 3) != 1)	/* TRM */
 | |
|         goto intr_update;
 | |
| 
 | |
|     if (!(s->control & 1) ||				/* SINGLE */
 | |
|                     (ch->config & (1 << 20))) {		/* FORCE */
 | |
|         if (ch->txrx)
 | |
|             ch->rx = ch->txrx(ch->opaque, ch->tx,	/* WL */
 | |
|                             1 + (0x1f & (ch->config >> 7)));
 | |
|     }
 | |
| 
 | |
|     ch->tx = 0;
 | |
|     ch->status |= 1 << 2;				/* EOT */
 | |
|     ch->status |= 1 << 1;				/* TXS */
 | |
|     if (((ch->config >> 12) & 3) != 2)			/* TRM */
 | |
|         ch->status |= 1 << 0;				/* RXS */
 | |
| 
 | |
| intr_update:
 | |
|     if ((ch->status & (1 << 0)) &&			/* RXS */
 | |
|                     ((ch->config >> 12) & 3) != 2 &&	/* TRM */
 | |
|                     !(ch->config & (1 << 19)))		/* TURBO */
 | |
|         s->irqst |= 1 << (2 + 4 * chnum);		/* RX_FULL */
 | |
|     if ((ch->status & (1 << 1)) &&			/* TXS */
 | |
|                     ((ch->config >> 12) & 3) != 1)	/* TRM */
 | |
|         s->irqst |= 1 << (0 + 4 * chnum);		/* TX_EMPTY */
 | |
|     omap_mcspi_interrupt_update(s);
 | |
|     omap_mcspi_dmarequest_update(ch);
 | |
| }
 | |
| 
 | |
| void omap_mcspi_reset(struct omap_mcspi_s *s)
 | |
| {
 | |
|     int ch;
 | |
| 
 | |
|     s->sysconfig = 0;
 | |
|     s->systest = 0;
 | |
|     s->irqst = 0;
 | |
|     s->irqen = 0;
 | |
|     s->wken = 0;
 | |
|     s->control = 4;
 | |
| 
 | |
|     for (ch = 0; ch < 4; ch ++) {
 | |
|         s->ch[ch].config = 0x060000;
 | |
|         s->ch[ch].status = 2;				/* TXS */
 | |
|         s->ch[ch].control = 0;
 | |
| 
 | |
|         omap_mcspi_dmarequest_update(s->ch + ch);
 | |
|     }
 | |
| 
 | |
|     omap_mcspi_interrupt_update(s);
 | |
| }
 | |
| 
 | |
| static uint32_t omap_mcspi_read(void *opaque, target_phys_addr_t addr)
 | |
| {
 | |
|     struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque;
 | |
|     int ch = 0;
 | |
|     uint32_t ret;
 | |
| 
 | |
|     switch (addr) {
 | |
|     case 0x00:	/* MCSPI_REVISION */
 | |
|         return 0x91;
 | |
| 
 | |
|     case 0x10:	/* MCSPI_SYSCONFIG */
 | |
|         return s->sysconfig;
 | |
| 
 | |
|     case 0x14:	/* MCSPI_SYSSTATUS */
 | |
|         return 1;					/* RESETDONE */
 | |
| 
 | |
|     case 0x18:	/* MCSPI_IRQSTATUS */
 | |
|         return s->irqst;
 | |
| 
 | |
|     case 0x1c:	/* MCSPI_IRQENABLE */
 | |
|         return s->irqen;
 | |
| 
 | |
|     case 0x20:	/* MCSPI_WAKEUPENABLE */
 | |
|         return s->wken;
 | |
| 
 | |
|     case 0x24:	/* MCSPI_SYST */
 | |
|         return s->systest;
 | |
| 
 | |
|     case 0x28:	/* MCSPI_MODULCTRL */
 | |
|         return s->control;
 | |
| 
 | |
|     case 0x68: ch ++;
 | |
|     case 0x54: ch ++;
 | |
|     case 0x40: ch ++;
 | |
|     case 0x2c:	/* MCSPI_CHCONF */
 | |
|         return s->ch[ch].config;
 | |
| 
 | |
|     case 0x6c: ch ++;
 | |
|     case 0x58: ch ++;
 | |
|     case 0x44: ch ++;
 | |
|     case 0x30:	/* MCSPI_CHSTAT */
 | |
|         return s->ch[ch].status;
 | |
| 
 | |
|     case 0x70: ch ++;
 | |
|     case 0x5c: ch ++;
 | |
|     case 0x48: ch ++;
 | |
|     case 0x34:	/* MCSPI_CHCTRL */
 | |
|         return s->ch[ch].control;
 | |
| 
 | |
|     case 0x74: ch ++;
 | |
|     case 0x60: ch ++;
 | |
|     case 0x4c: ch ++;
 | |
|     case 0x38:	/* MCSPI_TX */
 | |
|         return s->ch[ch].tx;
 | |
| 
 | |
|     case 0x78: ch ++;
 | |
|     case 0x64: ch ++;
 | |
|     case 0x50: ch ++;
 | |
|     case 0x3c:	/* MCSPI_RX */
 | |
|         s->ch[ch].status &= ~(1 << 0);			/* RXS */
 | |
|         ret = s->ch[ch].rx;
 | |
|         omap_mcspi_transfer_run(s, ch);
 | |
|         return ret;
 | |
|     }
 | |
| 
 | |
|     OMAP_BAD_REG(addr);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static void omap_mcspi_write(void *opaque, target_phys_addr_t addr,
 | |
|                 uint32_t value)
 | |
| {
 | |
|     struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque;
 | |
|     int ch = 0;
 | |
| 
 | |
|     switch (addr) {
 | |
|     case 0x00:	/* MCSPI_REVISION */
 | |
|     case 0x14:	/* MCSPI_SYSSTATUS */
 | |
|     case 0x30:	/* MCSPI_CHSTAT0 */
 | |
|     case 0x3c:	/* MCSPI_RX0 */
 | |
|     case 0x44:	/* MCSPI_CHSTAT1 */
 | |
|     case 0x50:	/* MCSPI_RX1 */
 | |
|     case 0x58:	/* MCSPI_CHSTAT2 */
 | |
|     case 0x64:	/* MCSPI_RX2 */
 | |
|     case 0x6c:	/* MCSPI_CHSTAT3 */
 | |
|     case 0x78:	/* MCSPI_RX3 */
 | |
|         OMAP_RO_REG(addr);
 | |
|         return;
 | |
| 
 | |
|     case 0x10:	/* MCSPI_SYSCONFIG */
 | |
|         if (value & (1 << 1))				/* SOFTRESET */
 | |
|             omap_mcspi_reset(s);
 | |
|         s->sysconfig = value & 0x31d;
 | |
|         break;
 | |
| 
 | |
|     case 0x18:	/* MCSPI_IRQSTATUS */
 | |
|         if (!((s->control & (1 << 3)) && (s->systest & (1 << 11)))) {
 | |
|             s->irqst &= ~value;
 | |
|             omap_mcspi_interrupt_update(s);
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     case 0x1c:	/* MCSPI_IRQENABLE */
 | |
|         s->irqen = value & 0x1777f;
 | |
|         omap_mcspi_interrupt_update(s);
 | |
|         break;
 | |
| 
 | |
|     case 0x20:	/* MCSPI_WAKEUPENABLE */
 | |
|         s->wken = value & 1;
 | |
|         break;
 | |
| 
 | |
|     case 0x24:	/* MCSPI_SYST */
 | |
|         if (s->control & (1 << 3))			/* SYSTEM_TEST */
 | |
|             if (value & (1 << 11)) {			/* SSB */
 | |
|                 s->irqst |= 0x1777f;
 | |
|                 omap_mcspi_interrupt_update(s);
 | |
|             }
 | |
|         s->systest = value & 0xfff;
 | |
|         break;
 | |
| 
 | |
|     case 0x28:	/* MCSPI_MODULCTRL */
 | |
|         if (value & (1 << 3))				/* SYSTEM_TEST */
 | |
|             if (s->systest & (1 << 11)) {		/* SSB */
 | |
|                 s->irqst |= 0x1777f;
 | |
|                 omap_mcspi_interrupt_update(s);
 | |
|             }
 | |
|         s->control = value & 0xf;
 | |
|         break;
 | |
| 
 | |
|     case 0x68: ch ++;
 | |
|     case 0x54: ch ++;
 | |
|     case 0x40: ch ++;
 | |
|     case 0x2c:	/* MCSPI_CHCONF */
 | |
|         if ((value ^ s->ch[ch].config) & (3 << 14))	/* DMAR | DMAW */
 | |
|             omap_mcspi_dmarequest_update(s->ch + ch);
 | |
|         if (((value >> 12) & 3) == 3)			/* TRM */
 | |
|             fprintf(stderr, "%s: invalid TRM value (3)\n", __FUNCTION__);
 | |
|         if (((value >> 7) & 0x1f) < 3)			/* WL */
 | |
|             fprintf(stderr, "%s: invalid WL value (%i)\n",
 | |
|                             __FUNCTION__, (value >> 7) & 0x1f);
 | |
|         s->ch[ch].config = value & 0x7fffff;
 | |
|         break;
 | |
| 
 | |
|     case 0x70: ch ++;
 | |
|     case 0x5c: ch ++;
 | |
|     case 0x48: ch ++;
 | |
|     case 0x34:	/* MCSPI_CHCTRL */
 | |
|         if (value & ~s->ch[ch].control & 1) {		/* EN */
 | |
|             s->ch[ch].control |= 1;
 | |
|             omap_mcspi_transfer_run(s, ch);
 | |
|         } else
 | |
|             s->ch[ch].control = value & 1;
 | |
|         break;
 | |
| 
 | |
|     case 0x74: ch ++;
 | |
|     case 0x60: ch ++;
 | |
|     case 0x4c: ch ++;
 | |
|     case 0x38:	/* MCSPI_TX */
 | |
|         s->ch[ch].tx = value;
 | |
|         s->ch[ch].status &= ~(1 << 1);			/* TXS */
 | |
|         omap_mcspi_transfer_run(s, ch);
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         OMAP_BAD_REG(addr);
 | |
|         return;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static CPUReadMemoryFunc * const omap_mcspi_readfn[] = {
 | |
|     omap_badwidth_read32,
 | |
|     omap_badwidth_read32,
 | |
|     omap_mcspi_read,
 | |
| };
 | |
| 
 | |
| static CPUWriteMemoryFunc * const omap_mcspi_writefn[] = {
 | |
|     omap_badwidth_write32,
 | |
|     omap_badwidth_write32,
 | |
|     omap_mcspi_write,
 | |
| };
 | |
| 
 | |
| struct omap_mcspi_s *omap_mcspi_init(struct omap_target_agent_s *ta, int chnum,
 | |
|                 qemu_irq irq, qemu_irq *drq, omap_clk fclk, omap_clk iclk)
 | |
| {
 | |
|     int iomemtype;
 | |
|     struct omap_mcspi_s *s = (struct omap_mcspi_s *)
 | |
|             g_malloc0(sizeof(struct omap_mcspi_s));
 | |
|     struct omap_mcspi_ch_s *ch = s->ch;
 | |
| 
 | |
|     s->irq = irq;
 | |
|     s->chnum = chnum;
 | |
|     while (chnum --) {
 | |
|         ch->txdrq = *drq ++;
 | |
|         ch->rxdrq = *drq ++;
 | |
|         ch ++;
 | |
|     }
 | |
|     omap_mcspi_reset(s);
 | |
| 
 | |
|     iomemtype = l4_register_io_memory(omap_mcspi_readfn,
 | |
|                     omap_mcspi_writefn, s);
 | |
|     omap_l4_attach(ta, 0, iomemtype);
 | |
| 
 | |
|     return s;
 | |
| }
 | |
| 
 | |
| void omap_mcspi_attach(struct omap_mcspi_s *s,
 | |
|                 uint32_t (*txrx)(void *opaque, uint32_t, int), void *opaque,
 | |
|                 int chipselect)
 | |
| {
 | |
|     if (chipselect < 0 || chipselect >= s->chnum)
 | |
|         hw_error("%s: Bad chipselect %i\n", __FUNCTION__, chipselect);
 | |
| 
 | |
|     s->ch[chipselect].txrx = txrx;
 | |
|     s->ch[chipselect].opaque = opaque;
 | |
| }
 |