From: <pal...@us...> - 2005-01-08 22:46:58
|
Update of /cvsroot/gc-linux/linux/drivers/exi In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv32197/drivers/exi Modified Files: Makefile Added Files: exi-bus.c exi-hw.c exi_priv.h Log Message: Initial EXI framework check-in, including BBA changes --- NEW FILE: exi-bus.c --- /* * drivers/exi/exi-bus.c * * Nintendo GameCube EXI driver * Copyright (C) 2004-2005 The GameCube Linux Team * Copyright (C) 2004,2005 Todd Jeffreys <to...@vo...> * * 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. * */ #include <linux/module.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/device.h> #include <linux/exi.h> #include <asm/io.h> #include "exi_priv.h" static int exi_match(struct device *dev,struct device_driver *drv); struct exi_interrupt_handlers irq_handlers[EXI_MAX_CHANNELS]; struct exi_locked_data exi_data[EXI_MAX_CHANNELS]; static struct exi_device exi_devices[EXI_MAX_CHANNELS][EXI_DEVICES_PER_CHANNEL]; static struct device exi_parent[EXI_MAX_CHANNELS] = { { .bus_id = "exi0", }, { .bus_id = "exi1", }, { .bus_id = "exi2", } }; static struct bus_type exi_bus_type = { .match = exi_match, .name = "exi" }; int exi_register_irq(int channel_irq,exi_irq_handler func,void *param) { void* __iomem reg; u32 csr; if (channel_irq < EXI_MAX_CHANNELS) { if (irq_handlers[channel_irq].func) { return -EBUSY; } irq_handlers[channel_irq].param = param; irq_handlers[channel_irq].func = func; /* now setup the hardware */ reg = EXI_CSR(channel_irq); csr = readl(reg); writel(csr | (EXI_CSR_EXIINT | EXI_CSR_EXIINTMASK),reg); return 0; } return -EINVAL; } void exi_unregister_irq(int channel_irq) { void * __iomem reg; u32 csr; if (channel_irq < EXI_MAX_CHANNELS) { irq_handlers[channel_irq].func = NULL; reg = EXI_CSR(channel_irq); csr = readl(reg); writel((csr | EXI_CSR_EXIINT) & ~EXI_CSR_EXIINTMASK,reg); } } static int exi_device_probe(struct device *dev) { struct exi_device *exi_device = to_exi_device(dev); struct exi_driver *exi_driver = to_exi_driver(dev->driver); int err = -ENODEV; if (exi_driver->probe) { err = exi_driver->probe(exi_device); } return err; } static int exi_device_remove(struct device *dev) { struct exi_device *exi_device = to_exi_device(dev); struct exi_driver *exi_driver = to_exi_driver(dev->driver); if (exi_driver->remove) { exi_driver->remove(exi_device); } return 0; } int exi_register_driver(struct exi_driver *driver) { driver->driver.name = driver->name; driver->driver.bus = &exi_bus_type; driver->driver.probe = exi_device_probe; driver->driver.remove = exi_device_remove; return driver_register(&driver->driver); } void exi_unregister_driver(struct exi_driver *driver) { driver_unregister(&driver->driver); } static int exi_match(struct device *dev,struct device_driver *drv) { /* return 1 if the driver can handle the device */ struct exi_device *exi_device = to_exi_device(dev); struct exi_driver *exi_driver = to_exi_driver(drv); if ((exi_device->eid.channel == exi_driver->eid.channel) && (exi_device->eid.device == exi_driver->eid.device) && (exi_device->eid.id == exi_driver->eid.id)) { return 1; } return 0; } static void exi_bus_scan(void) { unsigned int channel; unsigned int device; for (channel=0;channel<EXI_MAX_CHANNELS;++channel) { /* initialize the data per each exi bus */ exi_data[channel].exi_state = EXI_IDLE; exi_data[channel].cur_command = 0; atomic_set(&exi_data[channel].tc_interrupt,0); spin_lock_init(&exi_data[channel].queue_lock); INIT_LIST_HEAD(&exi_data[channel].queue); tasklet_init(&exi_data[channel].tasklet, exi_tasklet, (unsigned long)&exi_data[channel]); /* add the exi devices underneath the parents */ for (device=0;device<EXI_DEVICES_PER_CHANNEL;++device) { exi_devices[channel][device].eid.channel = channel; exi_devices[channel][device].eid.device = device; sprintf(exi_devices[channel][device].dev.bus_id, "dev%u",device); exi_devices[channel][device].dev.parent = &exi_parent[channel]; exi_devices[channel][device].dev.bus = &exi_bus_type; exi_devices[channel][device].dev.platform_data = &exi_data[channel]; /* now ID the device */ exi_devices[channel][device].eid.id = exi_synchronous_id(channel,device); if (exi_devices[channel][device].eid.id != EXI_INVALID_ID) { printk(KERN_INFO "%s:%s: %x\n", exi_parent[channel].bus_id, exi_devices[channel][device].dev.bus_id, exi_devices[channel][device].eid.id); device_register(&exi_devices[channel][device].dev); } } } } void exi_bus_insert(unsigned int channel,unsigned int bInsert) { /* this is all wrong, just skip this function, no hot-plug support for now */ /*u32 device; u32 id; if (bInsert) { for (device=0;device<EXI_DEVICES_PER_CHANNEL;++device) { id = exi_synchronous_id(channel,device); if (id != EXI_INVALID_ID) { device_register(&exi_devices[channel][device].dev); } } } else { device_register(&exi_devices[channel][0].dev); device_register(&exi_devices[channel][1].dev); device_register(&exi_devices[channel][2].dev); }*/ } static int __init exi_init(void) { int i; int r; /* acquire the interrupt */ printk(KERN_INFO "Initializing EXI interface\n"); /* register the bus */ if ((r=bus_register(&exi_bus_type))) { return r; } /* register root devices */ for (i=0;i<EXI_MAX_CHANNELS;++i) { if ((r=device_register(exi_parent+i))) { return r; } } irq_handlers[0].func = NULL; irq_handlers[1].func = NULL; irq_handlers[2].func = NULL; for (r=0;r<EXI_MAX_CHANNELS;++r) { /* wait for all transfers to complete */ while (readl(EXI_CR(r)) & EXI_MR_TSTART) { } /* clear interrupts and reset interrupts */ writel(EXI_CSR_TCINT | EXI_CSR_EXIINT | EXI_CSR_EXTINT | EXI_CSR_EXTINTMASK, EXI_CSR(r)); } /* now enumerate through the bus and add all detected devices */ exi_bus_scan(); return (request_irq(EXI_IRQ,exi_bus_irq_handler,0,"EXI",NULL)); } EXPORT_SYMBOL(exi_register_driver); EXPORT_SYMBOL(exi_unregister_driver); EXPORT_SYMBOL(exi_register_irq); EXPORT_SYMBOL(exi_unregister_irq); EXPORT_SYMBOL(exi_add_command_group); EXPORT_SYMBOL(exi_bus_type); postcore_initcall(exi_init); --- NEW FILE: exi-hw.c --- /* * drivers/exi/exi-hw.c * * Nintendo GameCube EXI driver * Copyright (C) 2004-2005 The GameCube Linux Team * Copyright (C) 2004,2005 Todd Jeffreys <to...@vo...> * * 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. * */ #include <linux/types.h> #include <linux/exi.h> #include <linux/wait.h> #include <linux/delay.h> #include <asm/io.h> #include "exi_priv.h" static inline void exi_select(struct exi_command_group *cmd) { const u32 channel = (cmd->flags >> 16) & 3; const u32 device = (cmd->flags >> 20) & 7; const u32 frequency = (cmd->flags >> 24) & 7; void * __iomem const reg = EXI_CSR(channel); u32 val = readl(reg); val &= (EXI_CSR_EXTINTMASK | EXI_CSR_TCINTMASK | EXI_CSR_EXIINTMASK); val |= (device << 7) | (frequency << 4); writel(val,reg); } static inline void exi_deselect(struct exi_command_group *cmd) { u32 channel = (cmd->flags >> 16) & 3; void * __iomem const reg = EXI_CSR(channel); u32 val; if (cmd->flags & EXI_DESELECT_UDELAY) { udelay(cmd->deselect_udelay); } val = readl(reg) & (EXI_CSR_EXTINTMASK | EXI_CSR_TCINTMASK | EXI_CSR_EXIINTMASK); writel(val,reg); } static void exi_immediate_transfer(struct exi_command *cmd, unsigned int channel) { void * __iomem const imm_reg = EXI_IMM(channel); void * __iomem const cr_reg = EXI_CR(channel); void * __iomem const csr_reg = EXI_CSR(channel); u32 val; void *data = cmd->data; u32 len = cmd->len; /* fast path, 4 bytes at a time */ while (len >= 4) { /* write to register */ if (cmd->flags & EXI_CMD_WRITE) val = *((u32*)data); else val = 0xFFFFFFFF; writel(val,imm_reg); /* go! */ val = EXI_MR_TSTART | EXI_MR_TLEN(4) | (cmd->flags & 7); writel(val,cr_reg); /* wait for completion */ while (readl(cr_reg) & EXI_MR_TSTART) { } /* clear the TCINT now */ writel(readl(csr_reg) | EXI_CSR_TCINT,csr_reg); /* store result on a read */ if (!(cmd->flags & EXI_CMD_WRITE)) { *((u32*)data) = readl(imm_reg); } /* change pointer and length */ data += 4; len -= 4; } /* if we have 1,2,3 remaining bytes */ if (len) { /* write to register */ if (cmd->flags & EXI_CMD_WRITE) { switch (len) { case 1: val = *((u8*)data) << 24; break; case 2: val = *((u16*)data) << 16; break; default: /* this is really 3, we read an entire 4 bytes at a time since there is no penalty for reading the extra byte. EXI hardware will ignore the extra byte anyways. */ val = *((u32*)data); break; } } else { val = 0xFFFFFFFF; } writel(val,imm_reg); /* go */ val = EXI_MR_TSTART | EXI_MR_TLEN(len) | (cmd->flags & 7); writel(val,cr_reg); /* wait for completion */ while (readl(cr_reg) & EXI_MR_TSTART) { } /* clear the TCINT now */ writel(readl(csr_reg) | EXI_CSR_TCINT,csr_reg); /* store result on a read */ if (!(cmd->flags & EXI_CMD_WRITE)) { val = readl(imm_reg); switch (len) { case 1: *((u8*)data) = (u8)(val >> 24); break; case 2: *((u16*)data) = (u16)(val >> 16); break; default: /* this is really a 3 byte read */ *((u16*)data) = (u16)(val >> 16); *((u8*)(data+2)) = (u8)(val >> 8); break; } } } } static void exi_dma_transfer(struct exi_command *subcmd, unsigned int channel) { void * __iomem reg; u32 val; /* flush the cache when writing */ if (subcmd->flags & EXI_CMD_WRITE) { flush_dcache_range((u32)subcmd->data, (u32)subcmd->data + subcmd->len); } /* convert to physical */ val = virt_to_phys(subcmd->data); writel(val,EXI_MAR(channel)); /* write length */ writel(subcmd->len,EXI_LENGTH(channel)); /* clear the IMM area */ writel(0xFFFFFFFF,EXI_IMM(channel)); /* enable the TC interrupt */ reg = EXI_CSR(channel); val = readl(reg) | EXI_CSR_TCINTMASK | EXI_CSR_TCINT; writel(val,reg); /* go! */ val = EXI_MR_TSTART | EXI_MR_DMA | (subcmd->flags & 7); writel(val,EXI_CR(channel)); } static void exi_complete_command(struct exi_locked_data *data, struct exi_command *subcmd) { struct exi_command_group *cmd; unsigned long flags; cmd = (struct exi_command_group*)data->queue.next; /* restore idle flag */ data->exi_state = EXI_IDLE; /* increment the count now */ data->cur_command++; if (data->cur_command >= cmd->num_commands) { /* deselect */ exi_deselect(cmd); /* remove from queue */ spin_lock_irqsave(&data->queue_lock,flags); list_del(&cmd->list); spin_unlock_irqrestore(&data->queue_lock,flags); /* reset pointer */ data->cur_command = 0; } /* call the callback */ if (subcmd->completion_routine) { subcmd->completion_routine(subcmd); } } static void exi_execute_queue(struct exi_locked_data *data) { u32 channel; struct exi_command_group *cmd; struct exi_command *subcmd; /* execute the first command, but do not remove until done */ cmd = (struct exi_command_group*)data->queue.next; channel = (cmd->flags >> 16) & 3; /* is this the first item? We must select it */ if (data->cur_command == 0) { exi_select(cmd); } subcmd = cmd->commands + data->cur_command; /* execute transfer, determine if we can use DMA */ if (((subcmd->len & (EXI_DMA_ALIGNMENT-1)) == 0) && (((u32)subcmd->data & (EXI_DMA_ALIGNMENT-1)) == 0)) { /* set state */ atomic_set(&data->tc_interrupt,0); data->exi_state = EXI_WAITING_FOR_TC; /* do it */ exi_dma_transfer(subcmd,channel); } else { exi_immediate_transfer(subcmd,channel); exi_complete_command(data,subcmd); } } u32 exi_synchronous_id(unsigned int channel,unsigned int device) { struct exi_command_group cmd; struct exi_command sub; u16 write; u32 read; cmd.flags = (channel << 16) | (EXI_DEVICE_0 << device) | EXI_FREQUENCY_3; write = 0; exi_select(&cmd); sub.flags = EXI_CMD_WRITE; sub.data = &write; sub.len = sizeof(write); exi_immediate_transfer(&sub,channel); sub.flags = EXI_CMD_READ; sub.data = &read; sub.len = sizeof(read); exi_immediate_transfer(&sub,channel); exi_deselect(&cmd); return read; } void exi_tasklet(unsigned long param) { unsigned long flags; int empty; struct exi_locked_data *data = (struct exi_locked_data*)param; struct exi_command_group *cmd; struct exi_command *subcmd; while (1) { /* check if the queue has stuff in it */ spin_lock_irqsave(&data->queue_lock,flags); empty = list_empty(&data->queue); spin_unlock_irqrestore(&data->queue_lock,flags); if (empty) { goto exit_tasklet; } /* stuff in queue, process it */ switch (data->exi_state) { case EXI_IDLE: exi_execute_queue(data); break; case EXI_WAITING_FOR_TC: /* no interrupt yet? */ if (!atomic_read(&data->tc_interrupt)) { goto exit_tasklet; } /* finish the operation */ cmd = (struct exi_command_group*)data->queue.next; subcmd = cmd->commands + data->cur_command; /* invalidate the cache on a read */ if (!(subcmd->flags & EXI_CMD_WRITE)) { invalidate_dcache_range((u32)subcmd->data, (u32)subcmd->data + subcmd->len); } /* complete the operation */ atomic_set(&data->tc_interrupt,0); exi_complete_command(data,subcmd); break; } } exit_tasklet: return; } irqreturn_t exi_bus_irq_handler(int irq,void *dev_id,struct pt_regs *regs) { void * __iomem reg; u32 csr; u32 val; int channel; for (channel = 0; channel < EXI_MAX_CHANNELS; channel++) { /* find interrupt cause */ reg = EXI_CSR(channel); csr = readl(reg); val = csr & (EXI_CSR_EXTINT | EXI_CSR_EXIINT | EXI_CSR_TCINT); if (!val) continue; /* ack */ writel(csr,reg); if (csr & EXI_CSR_EXTINT) { /* insert happened */ exi_bus_insert(channel,csr & EXI_CSR_EXT); } if ((csr & (EXI_CSR_TCINT | EXI_CSR_TCINTMASK)) == (EXI_CSR_TCINT | EXI_CSR_TCINTMASK)) { /* there's a weird thing happening, we tend to get these interrupts when the mask is off, so skip them if the mask if off */ /* get the device data based on the channel */ atomic_set(&exi_data[channel].tc_interrupt,1); tasklet_schedule(&exi_data[channel].tasklet); /* disable the TC interrupt */ csr &= ~EXI_CSR_TCINTMASK; csr |= EXI_CSR_TCINT; writel(csr,reg); } if (csr & EXI_CSR_EXIINT) { /* fire the callback associated with the irq */ if (irq_handlers[channel].func) { irq_handlers[channel].func( channel,irq_handlers[channel].param); } } } return IRQ_HANDLED; } void exi_add_command_group(struct exi_command_group *cmd,unsigned int count) { unsigned long flags; unsigned int i; unsigned int freq; struct exi_driver *drv; struct exi_locked_data *data = (struct exi_locked_data*)cmd->dev->dev.platform_data; spin_lock_irqsave(&data->queue_lock,flags); /* add to the queue */ for (i=0;i<count;++i) { /* modify the flags based on the device pull out the frequency from the driver */ drv = to_exi_driver(cmd->dev->dev.driver); freq = drv ? (drv->frequency << 24) : EXI_FREQUENCY_3; cmd->flags &= 0xFFFF; cmd->flags |= (cmd->dev->eid.channel << 16) | (EXI_DEVICE_0 << cmd->dev->eid.device) | freq; /* add to the list */ list_add_tail(&cmd[i].list,&data->queue); } spin_unlock_irqrestore(&data->queue_lock,flags); /* now start the tasklet */ tasklet_schedule(&data->tasklet); } --- NEW FILE: exi_priv.h --- /* * drivers/exi/exi_priv.h * * Nintendo GameCube EXI driver * Copyright (C) 2004-2005 The GameCube Linux Team * Copyright (C) 2004,2005 Todd Jeffreys <to...@vo...> * * 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. * */ #ifndef __exi_priv__ #define __exi_priv__ #include <linux/interrupt.h> #include <linux/list.h> #include <asm/atomic.h> /* Flags for exi_command->flags */ #define EXI_CHANNEL_0 (0x00000000) #define EXI_CHANNEL_1 (0x00010000) #define EXI_CHANNEL_2 (0x00020000) #define EXI_DEVICE_0 (0x00100000) #define EXI_DEVICE_1 (0x00200000) #define EXI_DEVICE_2 (0x00400000) #define EXI_FREQUENCY_0 (0x00000000) #define EXI_FREQUENCY_1 (0x01000000) #define EXI_FREQUENCY_2 (0x02000000) #define EXI_FREQUENCY_3 (0x03000000) #define EXI_FREQUENCY_4 (0x04000000) #define EXI_FREQUENCY_5 (0x05000000) #define _EXI_DEBUG 1 #define EXI_IRQ 4 #define EXI_MAX_CHANNELS 3 #define EXI_DEVICES_PER_CHANNEL 3 #define EXI_INVALID_ID 0xFFFFFFFF #define EXI_READ 0 #define EXI_WRITE 1 #define EXI_CSR_BASE (void* __iomem)0xCC006800 #define EXI_MAR_BASE (void* __iomem)0xCC006804 #define EXI_LENGTH_BASE (void* __iomem)0xCC006808 #define EXI_CR_BASE (void* __iomem)0xCC00680C #define EXI_IMM_DATA_BASE (void* __iomem)0xCC006810 #define EXI_CHANNEL_SPACING 0x14 #define EXI_CSR_EXT (1<<12) #define EXI_CSR_EXTINT (1<<11) #define EXI_CSR_EXTINTMASK (1<<10) #define EXI_CSR_CSMASK (0x7<<7) #define EXI_CSR_CS_0 (0x1<<7) /* Chip Select 001 */ #define EXI_CSR_CS_1 (0x2<<7) /* Chip Select 010 */ #define EXI_CSR_CS_2 (0x4<<7) /* Chip Select 100 */ #define EXI_CSR_CLKMASK (0x7<<4) #define EXI_CSR_CLK_1MHZ (0x0<<4) #define EXI_CSR_CLK_2MHZ (0x1<<4) #define EXI_CSR_CLK_4MHZ (0x2<<4) #define EXI_CSR_CLK_8MHZ (0x3<<4) #define EXI_CSR_CLK_16MHZ (0x4<<4) #define EXI_CSR_CLK_32MHZ (0x5<<4) #define EXI_CSR_TCINT (1<<3) #define EXI_CSR_TCINTMASK (1<<2) #define EXI_CSR_EXIINT (1<<1) #define EXI_CSR_EXIINTMASK (1<<0) #define EXI_MR_TSTART (1<<0) #define EXI_MR_DMA (1<<1) #define EXI_MR_READ (0<<2) #define EXI_MR_WRITE (1<<2) #define EXI_MR_READ_WRITE (2<<2) #define EXI_MR_TLEN(i) ((i-1)<<4) #define EXI_CSR(c) (EXI_CSR_BASE + (c*EXI_CHANNEL_SPACING)) #define EXI_MAR(c) (EXI_MAR_BASE + (c*EXI_CHANNEL_SPACING)) #define EXI_LENGTH(c) (EXI_LENGTH_BASE + (c*EXI_CHANNEL_SPACING)) #define EXI_CR(c) (EXI_CR_BASE + (c*EXI_CHANNEL_SPACING)) #define EXI_IMM(c) (EXI_IMM_DATA_BASE + (c*EXI_CHANNEL_SPACING)) #define EXI0_CSR EXI_CSR(0) #define EXI1_CSR EXI_CSR(1) #define EXI2_CSR EXI_CSR(2) struct exi_locked_data { /* per bus information */ spinlock_t queue_lock; struct list_head queue; unsigned int cur_command; struct tasklet_struct tasklet; enum { EXI_IDLE, EXI_WAITING_FOR_TC } exi_state; atomic_t tc_interrupt; }; struct exi_interrupt_handlers { exi_irq_handler func; void *param; }; extern struct exi_interrupt_handlers irq_handlers[EXI_MAX_CHANNELS]; extern struct exi_locked_data exi_data[EXI_MAX_CHANNELS]; void exi_tasklet(unsigned long param); irqreturn_t exi_bus_irq_handler(int irq,void *dev_id,struct pt_regs *regs); void exi_bus_insert(unsigned int channel,unsigned int bInsert); u32 exi_synchronous_id(unsigned int channel,unsigned int device); #endif Index: Makefile =================================================================== RCS file: /cvsroot/gc-linux/linux/drivers/exi/Makefile,v retrieving revision 1.2 retrieving revision 1.3 diff -u -d -r1.2 -r1.3 --- Makefile 19 Oct 2004 22:49:40 -0000 1.2 +++ Makefile 8 Jan 2005 22:46:47 -0000 1.3 @@ -2,4 +2,4 @@ # Makefile for the EXI bus core. # -obj-$(CONFIG_EXI) += exi-driver.o gcn-exi-lite.o +obj-$(CONFIG_EXI) += exi-bus.o exi-hw.o |