/* * Core maple bus functionality * * Copyright (C) 2007 Adrian McMenamin * * Based on 2.4 code by: * * Copyright (C) 2000-2001 YAEGASHI Takeshi * Copyright (C) 2001 M. R. Brown * Copyright (C) 2001 Paul Mundt * * and others. * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MODULE_AUTHOR("Yaegshi Takeshi, Paul Mundt, M.R. Brown, Adrian McMenamin"); MODULE_DESCRIPTION("Maple bus driver for Dreamcast"); MODULE_LICENSE("GPL v2"); MODULE_SUPPORTED_DEVICE("{{SEGA, Dreamcast/Maple}}"); static void maple_dma_handler(struct work_struct *work); static void maple_vblank_handler(struct work_struct *work); static DECLARE_WORK(maple_dma_process, maple_dma_handler); static DECLARE_WORK(maple_vblank_process, maple_vblank_handler); static LIST_HEAD(maple_waitq); static LIST_HEAD(maple_sentq); static DEFINE_MUTEX(maple_list_lock); static struct maple_driver maple_dummy_driver; static struct device maple_bus; static int subdevice_map[MAPLE_PORTS]; static unsigned long *maple_sendbuf, *maple_sendptr, *maple_lastptr; static unsigned long maple_pnp_time; static int started, scanning, liststatus; static struct kmem_cache *maple_queue_cache; struct maple_device_specify { int port; int unit; }; /** * maple_driver_register - register a device driver * automatically makes the driver bus a maple bus * @drv: the driver to be registered */ int maple_driver_register(struct device_driver *drv) { if (!drv) return -EINVAL; drv->bus = &maple_bus_type; return driver_register(drv); } EXPORT_SYMBOL_GPL(maple_driver_register); /* set hardware registers to enable next round of dma */ static void maplebus_dma_reset(void) { ctrl_outl(MAPLE_MAGIC, MAPLE_RESET); /* set trig type to 0 for software trigger, 1 for hardware (VBLANK) */ ctrl_outl(1, MAPLE_TRIGTYPE); ctrl_outl(MAPLE_2MBPS | MAPLE_TIMEOUT(50000), MAPLE_SPEED); ctrl_outl(PHYSADDR(maple_sendbuf), MAPLE_DMAADDR); ctrl_outl(1, MAPLE_ENABLE); } /** * maple_getcond_callback - setup handling MAPLE_COMMAND_GETCOND * @dev: device responding * @callback: handler callback * @interval: interval in jiffies between callbacks * @function: the function code for the device */ void maple_getcond_callback(struct maple_device *dev, void (*callback) (struct mapleq * mq), unsigned long interval, unsigned long function) { dev->callback = callback; dev->interval = interval; dev->function = cpu_to_be32(function); dev->when = jiffies; } EXPORT_SYMBOL_GPL(maple_getcond_callback); static int maple_dma_done(void) { return (ctrl_inl(MAPLE_STATE) & 1) == 0; } static void maple_release_device(struct device *dev) { if (dev->type) { kfree(dev->type->name); kfree(dev->type); } } /** * maple_add_packet - add a single instruction to the queue * @mq: instruction to add to waiting queue */ void maple_add_packet(struct mapleq *mq) { mutex_lock(&maple_list_lock); list_add(&mq->list, &maple_waitq); mutex_unlock(&maple_list_lock); } EXPORT_SYMBOL_GPL(maple_add_packet); static struct mapleq *maple_allocq(struct maple_device *dev) { struct mapleq *mq; mq = kmalloc(sizeof(*mq), GFP_KERNEL); if (!mq) return NULL; mq->dev = dev; mq->recvbufdcsp = kmem_cache_zalloc(maple_queue_cache, GFP_KERNEL); mq->recvbuf = (void *) P2SEGADDR(mq->recvbufdcsp); if (!mq->recvbuf) { kfree(mq); return NULL; } return mq; } static struct maple_device *maple_alloc_dev(int port, int unit) { struct maple_device *dev; dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return NULL; dev->port = port; dev->unit = unit; dev->mq = maple_allocq(dev); if (!dev->mq) { kfree(dev); return NULL; } return dev; } static void maple_free_dev(struct maple_device *mdev) { if (!mdev) return; if (mdev->mq) { kmem_cache_free(maple_queue_cache, mdev->mq->recvbufdcsp); kfree(mdev->mq); } kfree(mdev); } /* process the command queue into a maple command block * terminating command has bit 32 of first long set to 0 */ static void maple_build_block(struct mapleq *mq) { int port, unit, from, to, len; unsigned long *lsendbuf = mq->sendbuf; port = mq->dev->port & 3; unit = mq->dev->unit; len = mq->length; from = port << 6; to = (port << 6) | (unit > 0 ? (1 << (unit - 1)) & 0x1f : 0x20); *maple_lastptr &= 0x7fffffff; maple_lastptr = maple_sendptr; *maple_sendptr++ = (port << 16) | len | 0x80000000; *maple_sendptr++ = PHYSADDR(mq->recvbuf); *maple_sendptr++ = mq->command | (to << 8) | (from << 16) | (len << 24); while (len-- > 0) *maple_sendptr++ = *lsendbuf++; } /* build up command queue */ static void maple_send(void) { int i; int maple_packets; struct mapleq *mq, *nmq; if (!list_empty(&maple_sentq)) return; if (list_empty(&maple_waitq) || !maple_dma_done()) return; maple_packets = 0; maple_sendptr = maple_lastptr = maple_sendbuf; list_for_each_entry_safe(mq, nmq, &maple_waitq, list) { maple_build_block(mq); list_move(&mq->list, &maple_sentq); if (maple_packets++ > MAPLE_MAXPACKETS) break; } if (maple_packets > 0) { for (i = 0; i < (1 << MAPLE_DMA_PAGES); i++) dma_cache_sync(0, maple_sendbuf + i * PAGE_SIZE, PAGE_SIZE, DMA_BIDIRECTIONAL); } } static int attach_matching_maple_driver(struct device_driver *driver, void *devptr) { struct maple_driver *maple_drv; struct maple_device *mdev; mdev = devptr; maple_drv = to_maple_driver(driver); if (mdev->devinfo.function & be32_to_cpu(maple_drv->function)) { if (maple_drv->connect(mdev) == 0) { mdev->driver = maple_drv; return 1; } } return 0; } static void maple_detach_driver(struct maple_device *mdev) { if (!mdev) return; if (mdev->driver) { if (mdev->driver->disconnect) mdev->driver->disconnect(mdev); } mdev->driver = NULL; if (mdev->registered) { maple_release_device(&mdev->dev); device_unregister(&mdev->dev); } mdev->registered = 0; maple_free_dev(mdev); } /* process initial MAPLE_COMMAND_DEVINFO for each device or port */ static void maple_attach_driver(struct maple_device *dev) { char *p; char *recvbuf; unsigned long function; int matched, retval; recvbuf = dev->mq->recvbuf; memcpy(&dev->devinfo, recvbuf + 4, sizeof(dev->devinfo)); memcpy(dev->product_name, dev->devinfo.product_name, 30); memcpy(dev->product_licence, dev->devinfo.product_licence, 60); dev->product_name[30] = '\0'; dev->product_licence[60] = '\0'; for (p = dev->product_name + 29; dev->product_name <= p; p--) if (*p == ' ') *p = '\0'; else break; for (p = dev->product_licence + 59; dev->product_licence <= p; p--) if (*p == ' ') *p = '\0'; else break; function = be32_to_cpu(dev->devinfo.function); if (function > 0x200) { /* Do this silently - as not a real device */ function = 0; dev->driver = &maple_dummy_driver; sprintf(dev->dev.bus_id, "%d:0.port", dev->port); } else { printk(KERN_INFO "Maple bus at (%d, %d): Connected function 0x%lX\n", dev->port, dev->unit, function); matched = bus_for_each_drv(&maple_bus_type, NULL, dev, attach_matching_maple_driver); if (matched == 0) { /* Driver does not exist yet */ printk(KERN_INFO "No maple driver found for this device\n"); dev->driver = &maple_dummy_driver; } sprintf(dev->dev.bus_id, "%d:0%d.%lX", dev->port, dev->unit, function); } dev->function = function; dev->dev.bus = &maple_bus_type; dev->dev.parent = &maple_bus; dev->dev.release = &maple_release_device; retval = device_register(&dev->dev); if (retval) { printk(KERN_INFO "Maple bus: Attempt to register device (%x, %x) failed.\n", dev->port, dev->unit); maple_free_dev(dev); } dev->registered = 1; } /* * if device has been registered for the given * port and unit then return 1 - allows identification * of which devices need to be attached or detached */ static int detach_maple_device(struct device *device, void *portptr) { struct maple_device_specify *ds; struct maple_device *mdev; ds = portptr; mdev = to_maple_dev(device); if (mdev->port == ds->port && mdev->unit == ds->unit) return 1; return 0; } static int setup_maple_commands(struct device *device, void *ignored) { struct maple_device *maple_dev = to_maple_dev(device); if ((maple_dev->interval > 0) && time_after(jiffies, maple_dev->when)) { maple_dev->when = jiffies + maple_dev->interval; maple_dev->mq->command = MAPLE_COMMAND_GETCOND; maple_dev->mq->sendbuf = &maple_dev->function; maple_dev->mq->length = 1; maple_add_packet(maple_dev->mq); liststatus++; } else { if (time_after(jiffies, maple_pnp_time)) { maple_dev->mq->command = MAPLE_COMMAND_DEVINFO; maple_dev->mq->length = 0; maple_add_packet(maple_dev->mq); liststatus++; } } return 0; } /* VBLANK bottom half - implemented via workqueue */ static void maple_vblank_handler(struct work_struct *work) { if (!maple_dma_done()) return; if (!list_empty(&maple_sentq)) return; ctrl_outl(0, MAPLE_ENABLE); liststatus = 0; bus_for_each_dev(&maple_bus_type, NULL, NULL, setup_maple_commands); if (time_after(jiffies, maple_pnp_time)) maple_pnp_time = jiffies + MAPLE_PNP_INTERVAL; if (liststatus && list_empty(&maple_sentq)) { INIT_LIST_HEAD(&maple_sentq); maple_send(); } maplebus_dma_reset(); } /* handle devices added via hotplugs - placing them on queue for DEVINFO*/ static void maple_map_subunits(struct maple_device *mdev, int submask) { int retval, k, devcheck; struct maple_device *mdev_add; struct maple_device_specify ds; for (k = 0; k < 5; k++) { ds.port = mdev->port; ds.unit = k + 1; retval = bus_for_each_dev(&maple_bus_type, NULL, &ds, detach_maple_device); if (retval) { submask = submask >> 1; continue; } devcheck = submask & 0x01; if (devcheck) { mdev_add = maple_alloc_dev(mdev->port, k + 1); if (!mdev_add) return; mdev_add->mq->command = MAPLE_COMMAND_DEVINFO; mdev_add->mq->length = 0; maple_add_packet(mdev_add->mq); scanning = 1; } submask = submask >> 1; } } /* mark a device as removed */ static void maple_clean_submap(struct maple_device *mdev) { int killbit; killbit = (mdev->unit > 0 ? (1 << (mdev->unit - 1)) & 0x1f : 0x20); killbit = ~killbit; killbit &= 0xFF; subdevice_map[mdev->port] = subdevice_map[mdev->port] & killbit; } /* handle empty port or hotplug removal */ static void maple_response_none(struct maple_device *mdev, struct mapleq *mq) { if (mdev->unit != 0) { list_del(&mq->list); maple_clean_submap(mdev); printk(KERN_INFO "Maple bus device detaching at (%d, %d)\n", mdev->port, mdev->unit); maple_detach_driver(mdev); return; } if (!started) { printk(KERN_INFO "No maple devices attached to port %d\n", mdev->port); return; } maple_clean_submap(mdev); } /* preprocess hotplugs or scans */ static void maple_response_devinfo(struct maple_device *mdev, char *recvbuf) { char submask; if ((!started) || (scanning == 2)) { maple_attach_driver(mdev); return; } if (mdev->unit == 0) { submask = recvbuf[2] & 0x1F; if (submask ^ subdevice_map[mdev->port]) { maple_map_subunits(mdev, submask); subdevice_map[mdev->port] = submask; } } } /* maple dma end bottom half - implemented via workqueue */ static void maple_dma_handler(struct work_struct *work) { struct mapleq *mq, *nmq; struct maple_device *dev; char *recvbuf; enum maple_code code; if (!maple_dma_done()) return; ctrl_outl(0, MAPLE_ENABLE); if (!list_empty(&maple_sentq)) { list_for_each_entry_safe(mq, nmq, &maple_sentq, list) { recvbuf = mq->recvbuf; code = recvbuf[0]; dev = mq->dev; switch (code) { case MAPLE_RESPONSE_NONE: maple_response_none(dev, mq); break; case MAPLE_RESPONSE_DEVINFO: maple_response_devinfo(dev, recvbuf); break; case MAPLE_RESPONSE_DATATRF: if (dev->callback) dev->callback(mq); break; case MAPLE_RESPONSE_FILEERR: case MAPLE_RESPONSE_AGAIN: case MAPLE_RESPONSE_BADCMD: case MAPLE_RESPONSE_BADFUNC: printk(KERN_DEBUG "Maple non-fatal error 0x%X\n", code); break; case MAPLE_RESPONSE_ALLINFO: printk(KERN_DEBUG "Maple - extended device information not supported\n"); break; case MAPLE_RESPONSE_OK: break; default: break; } } INIT_LIST_HEAD(&maple_sentq); if (scanning == 1) { maple_send(); scanning = 2; } else scanning = 0; if (started == 0) started = 1; } maplebus_dma_reset(); } static irqreturn_t maplebus_dma_interrupt(int irq, void *dev_id) { /* Load everything into the bottom half */ schedule_work(&maple_dma_process); return IRQ_HANDLED; } static irqreturn_t maplebus_vblank_interrupt(int irq, void *dev_id) { schedule_work(&maple_vblank_process); return IRQ_HANDLED; } static struct irqaction maple_dma_irq = { .name = "maple bus DMA handler", .handler = maplebus_dma_interrupt, .flags = IRQF_SHARED, }; static struct irqaction maple_vblank_irq = { .name = "maple bus VBLANK handler", .handler = maplebus_vblank_interrupt, .flags = IRQF_SHARED, }; static int maple_set_dma_interrupt_handler(void) { return setup_irq(HW_EVENT_MAPLE_DMA, &maple_dma_irq); } static int maple_set_vblank_interrupt_handler(void) { return setup_irq(HW_EVENT_VSYNC, &maple_vblank_irq); } static int maple_get_dma_buffer(void) { maple_sendbuf = (void *) __get_free_pages(GFP_KERNEL | __GFP_ZERO, MAPLE_DMA_PAGES); if (!maple_sendbuf) return -ENOMEM; return 0; } static int match_maple_bus_driver(struct device *devptr, struct device_driver *drvptr) { struct maple_driver *maple_drv; struct maple_device *maple_dev; maple_drv = container_of(drvptr, struct maple_driver, drv); maple_dev = container_of(devptr, struct maple_device, dev); /* Trap empty port case */ if (maple_dev->devinfo.function == 0xFFFFFFFF) return 0; else if (maple_dev->devinfo.function & be32_to_cpu(maple_drv->function)) return 1; return 0; } static int maple_bus_uevent(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size) { return 0; } static void maple_bus_release(struct device *dev) { } static struct maple_driver maple_dummy_driver = { .drv = { .name = "maple_dummy_driver", .bus = &maple_bus_type, }, }; struct bus_type maple_bus_type = { .name = "maple", .match = match_maple_bus_driver, .uevent = maple_bus_uevent, }; EXPORT_SYMBOL_GPL(maple_bus_type); static struct device maple_bus = { .bus_id = "maple", .release = maple_bus_release, }; static int __init maple_bus_init(void) { int retval, i; struct maple_device *mdev[MAPLE_PORTS]; ctrl_outl(0, MAPLE_STATE); retval = device_register(&maple_bus); if (retval) goto cleanup; retval = bus_register(&maple_bus_type); if (retval) goto cleanup_device; retval = driver_register(&maple_dummy_driver.drv); if (retval) goto cleanup_bus; /* allocate memory for maple bus dma */ retval = maple_get_dma_buffer(); if (retval) { printk(KERN_INFO "Maple bus: Failed to allocate Maple DMA buffers\n"); goto cleanup_basic; } /* set up DMA interrupt handler */ retval = maple_set_dma_interrupt_handler(); if (retval) { printk(KERN_INFO "Maple bus: Failed to grab maple DMA IRQ\n"); goto cleanup_dma; } /* set up VBLANK interrupt handler */ retval = maple_set_vblank_interrupt_handler(); if (retval) { printk(KERN_INFO "Maple bus: Failed to grab VBLANK IRQ\n"); goto cleanup_irq; } maple_queue_cache = kmem_cache_create("maple_queue_cache", 0x400, 0, SLAB_HWCACHE_ALIGN, NULL); if (!maple_queue_cache) goto cleanup_bothirqs; /* setup maple ports */ for (i = 0; i < MAPLE_PORTS; i++) { mdev[i] = maple_alloc_dev(i, 0); if (!mdev[i]) { while (i-- > 0) maple_free_dev(mdev[i]); goto cleanup_cache; } mdev[i]->registered = 0; mdev[i]->mq->command = MAPLE_COMMAND_DEVINFO; mdev[i]->mq->length = 0; maple_attach_driver(mdev[i]); maple_add_packet(mdev[i]->mq); subdevice_map[i] = 0; } /* setup maplebus hardware */ maplebus_dma_reset(); /* initial detection */ maple_send(); maple_pnp_time = jiffies; printk(KERN_INFO "Maple bus core now registered.\n"); return 0; cleanup_cache: kmem_cache_destroy(maple_queue_cache); cleanup_bothirqs: free_irq(HW_EVENT_VSYNC, 0); cleanup_irq: free_irq(HW_EVENT_MAPLE_DMA, 0); cleanup_dma: free_pages((unsigned long) maple_sendbuf, MAPLE_DMA_PAGES); cleanup_basic: driver_unregister(&maple_dummy_driver.drv); cleanup_bus: bus_unregister(&maple_bus_type); cleanup_device: device_unregister(&maple_bus); cleanup: printk(KERN_INFO "Maple bus registration failed\n"); return retval; } subsys_initcall(maple_bus_init);