r4698 - developers/werner/wlan-spi/patches
werner at docs.openmoko.org
werner at docs.openmoko.org
Tue Oct 7 17:07:33 CEST 2008
Author: werner
Date: 2008-10-07 17:07:33 +0200 (Tue, 07 Oct 2008)
New Revision: 4698
Added:
developers/werner/wlan-spi/patches/hif-linux-sdio.patch
Removed:
developers/werner/wlan-spi/patches/wlan-spi-hif.patch
Modified:
developers/werner/wlan-spi/patches/series
Log:
Rename wlan-spi-hif.patch to hif-linux-sdio.patch, following the same naming as
the rest of this patch stack.
Copied: developers/werner/wlan-spi/patches/hif-linux-sdio.patch (from rev 4697, developers/werner/wlan-spi/patches/wlan-spi-hif.patch)
===================================================================
--- developers/werner/wlan-spi/patches/hif-linux-sdio.patch (rev 0)
+++ developers/werner/wlan-spi/patches/hif-linux-sdio.patch 2008-10-07 15:07:33 UTC (rev 4698)
@@ -0,0 +1,656 @@
+hif-linux-sdio.patch
+
+This is a replacement for Atheros' HIF layer. We use the Linux SDIO
+stack.
+
+Using Atheros' GPLv2 clause for now. Since there's only very little of
+the original code left, we should perhaps switch to GPL>=v2, which is
+preferred by Openmoko.
+
+Work in progress.
+
+Not-Yet-Signed-off-by: Werner Almesberger <werner at openmoko.org>
+
+Index: korig/drivers/ar6000/Makefile
+===================================================================
+--- korig.orig/drivers/ar6000/Makefile 2008-10-07 13:00:30.000000000 -0200
++++ korig/drivers/ar6000/Makefile 2008-10-07 13:00:32.000000000 -0200
+@@ -21,7 +21,7 @@
+ htc/htc_recv.o \
+ htc/htc_services.o \
+ htc/htc.o \
+- hif/hif.o \
++ hif/hif2.o \
+ bmi/bmi.o \
+ ar6000/ar6000_drv.o \
+ ar6000/ar6000_raw_if.o \
+Index: korig/drivers/ar6000/hif/hif2.c
+===================================================================
+--- /dev/null 1970-01-01 00:00:00.000000000 +0000
++++ korig/drivers/ar6000/hif/hif2.c 2008-10-07 13:00:43.000000000 -0200
+@@ -0,0 +1,625 @@
++/*
++ * hif2.c - HIF layer re-implementation for the Linux SDIO stack
++ *
++ * Copyright (C) 2008 by OpenMoko, Inc.
++ * Written by Werner Almesberger <werner at openmoko.org>
++ * All Rights Reserved
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation;
++ *
++ * Based on:
++ *
++ * @abstract: HIF layer reference implementation for Atheros SDIO stack
++ * @notice: Copyright (c) 2004-2006 Atheros Communications Inc.
++ */
++
++
++#include <linux/kernel.h>
++#include <linux/kthread.h>
++#include <linux/list.h>
++#include <linux/wait.h>
++#include <linux/spinlock.h>
++#include <linux/mmc/sdio_func.h>
++#include <linux/mmc/sdio.h>
++#include <asm/gpio.h>
++
++#include "athdefs.h"
++#include "a_types.h"
++#include "hif.h"
++
++
++/*
++ * KNOWN BUGS:
++ *
++ * - HIF_DEVICE_IRQ_ASYNC_SYNC doesn't work yet (gets MMC errors)
++ * - does not work with S3C SDI (probably due to bugs in the latter)
++ * - not ready for S3C SPI yet
++ * - no cleanup on setup errors yet
++ * - driver doesn't remove cleanly yet
++ * - latency can reach hundreds of ms, probably because of scheduling delays
++ * - packets go through about three queues before finally hitting the network
++ */
++
++
++//#define dev_dbg dev_info
++#define dev_dbg(dev, ...) ((void) dev)
++
++#define MBOXES 4
++
++#define HIF_MBOX_BLOCK_SIZE 128
++#define HIF_MBOX_BASE_ADDR 0x800
++#define HIF_MBOX_WIDTH 0x800
++#define HIF_MBOX_START_ADDR(mbox) \
++ (HIF_MBOX_BASE_ADDR+(mbox)*HIF_MBOX_WIDTH)
++
++
++struct hif_device {
++ void *htc_handle;
++ struct sdio_func *func;
++
++ /*
++ * @@@ our sweet little bit of bogosity - the mechanism that lets us
++ * use the SDIO stack from softirqs. This really wants to use skbs.
++ */
++ struct list_head queue;
++ spinlock_t queue_lock;
++ struct task_struct *io_task;
++ wait_queue_head_t wait;
++};
++
++struct hif_request {
++ struct list_head list;
++ struct sdio_func *func;
++ int (*read)(struct sdio_func *func,
++ void *dst, unsigned int addr, int count);
++ int (*write)(struct sdio_func *func,
++ unsigned int addr, void *src, int count);
++ void *buf;
++ unsigned long addr;
++ int len;
++ A_STATUS (*completion)(void *context, A_STATUS status);
++ void *context;
++};
++
++
++static HIF_DEVICE hif_device;
++static HTC_CALLBACKS htcCallbacks;
++
++
++int HIFInit(HTC_CALLBACKS *callbacks)
++{
++ BUG_ON(!callbacks);
++
++ printk(KERN_INFO "HIFInit\n");
++ htcCallbacks = *callbacks;
++
++ return 0;
++}
++
++
++static A_STATUS process_request(struct hif_request *req)
++{
++ int ret;
++ A_STATUS status;
++
++ dev_dbg(&req->func->dev, "process_request(req %p)\n", req);
++ sdio_claim_host(req->func);
++ if (req->read)
++ ret = req->read(req->func, req->buf, req->addr, req->len);
++ else
++ ret = req->write(req->func, req->addr, req->buf, req->len);
++ sdio_release_host(req->func);
++ status = ret ? A_ERROR : A_OK;
++ if (req->completion)
++ req->completion(req->context, status);
++ kfree(req);
++ return status;
++}
++
++
++static void enqueue_request(struct hif_device *hif, struct hif_request *req)
++{
++ unsigned long flags;
++
++ dev_dbg(&req->func->dev, "enqueue_request(req %p)\n", req);
++ spin_lock_irqsave(&hif->queue_lock, flags);
++ list_add_tail(&req->list, &hif->queue);
++ spin_unlock_irqrestore(&hif->queue_lock, flags);
++ wake_up(&hif->wait);
++}
++
++
++static struct hif_request *dequeue_request(struct hif_device *hif)
++{
++ struct hif_request *req;
++ unsigned long flags;
++
++ spin_lock_irqsave(&hif->queue_lock, flags);
++ if (list_empty(&hif->queue))
++ req = NULL;
++ else {
++ req = list_first_entry(&hif->queue,
++ struct hif_request, list);
++ list_del(&req->list);
++ }
++ spin_unlock_irqrestore(&hif->queue_lock, flags);
++ return req;
++}
++
++
++static int io(void *data)
++{
++ struct hif_device *hif = data;
++ DECLARE_WAITQUEUE(wait, current);
++ struct hif_request *req;
++
++ while (1) {
++ add_wait_queue(&hif->wait, &wait);
++ while (1) {
++ set_current_state(TASK_INTERRUPTIBLE);
++ if (signal_pending(current)) {
++ req = NULL;
++ break;
++ }
++ req = dequeue_request(hif);
++ if (req)
++ break;
++ schedule();
++ }
++ set_current_state(TASK_RUNNING);
++ remove_wait_queue(&hif->wait, &wait);
++
++ if (!req)
++ break;
++
++ (void) process_request(req);
++ }
++ return 0;
++}
++
++
++A_STATUS HIFReadWrite(HIF_DEVICE *hif, A_UINT32 address, A_UCHAR *buffer,
++ A_UINT32 length, A_UINT32 request, void *context)
++{
++ struct device *dev = HIFGetOSDevice(hif);
++ struct hif_request *req;
++
++ dev_dbg(dev, "HIFReadWrite(device %p, address 0x%x, buffer %p, "
++ "length %d, request 0x%x, context %p)\n",
++ hif, address, buffer, length, request, context);
++
++ BUG_ON(!(request & (HIF_SYNCHRONOUS | HIF_ASYNCHRONOUS)));
++ BUG_ON(!(request & (HIF_BYTE_BASIS | HIF_BLOCK_BASIS)));
++ BUG_ON(!(request & (HIF_READ | HIF_WRITE)));
++ BUG_ON(!(request & HIF_EXTENDED_IO));
++
++ if (address >= HIF_MBOX_START_ADDR(0) &&
++ address < HIF_MBOX_START_ADDR(MBOXES+1)) {
++ BUG_ON(length > HIF_MBOX_WIDTH);
++ /* Adjust the address so that the last byte falls on the EOM
++ address. */
++ address += HIF_MBOX_WIDTH-length;
++ }
++
++ req = kzalloc(sizeof(*req), GFP_ATOMIC);
++ if (!req) {
++ if (request & HIF_ASYNCHRONOUS)
++ htcCallbacks.rwCompletionHandler(context, A_ERROR);
++ return A_ERROR;
++ }
++
++ req->func = hif->func;
++ req->addr = address;
++ req->buf = buffer;
++ req->len = length;
++
++ if (request & HIF_READ) {
++ if (request & HIF_FIXED_ADDRESS)
++ req->read = sdio_readsb;
++ else
++ req->read = sdio_memcpy_fromio;
++ } else {
++ if (request & HIF_FIXED_ADDRESS)
++ req->write = sdio_writesb;
++ else
++ req->write = sdio_memcpy_toio;
++ }
++
++ if (!(request & HIF_ASYNCHRONOUS))
++ return process_request(req);
++
++ req->completion = htcCallbacks.rwCompletionHandler;
++ req->context = context;
++ enqueue_request(hif, req);
++
++ return A_OK;
++}
++
++
++A_STATUS HIFConfigureDevice(HIF_DEVICE *hif,
++ HIF_DEVICE_CONFIG_OPCODE opcode, void *config, A_UINT32 configLen)
++{
++ struct device *dev = HIFGetOSDevice(hif);
++ HIF_DEVICE_IRQ_PROCESSING_MODE *ipm_cfg = config;
++ A_UINT32 *mbs_cfg = config;
++ int i;
++
++ dev_dbg(dev, "HIFConfigureDevice\n");
++
++ switch (opcode) {
++ case HIF_DEVICE_GET_MBOX_BLOCK_SIZE:
++ for (i = 0; i != MBOXES; i++)
++ mbs_cfg[i] = HIF_MBOX_BLOCK_SIZE;
++ break;
++ case HIF_DEVICE_GET_MBOX_ADDR:
++ for (i = 0; i != MBOXES; i++)
++ mbs_cfg[i] = HIF_MBOX_START_ADDR(i);
++ break;
++ case HIF_DEVICE_GET_IRQ_PROC_MODE:
++ *ipm_cfg = HIF_DEVICE_IRQ_SYNC_ONLY;
++// *ipm_cfg = HIF_DEVICE_IRQ_ASYNC_SYNC;
++ break;
++ default:
++ return A_ERROR;
++ }
++ return A_OK;
++}
++
++
++void HIFShutDownDevice(HIF_DEVICE *hif)
++{
++ struct device *dev = HIFGetOSDevice(hif);
++
++ dev_dbg(dev, "HIFShutDownDevice\n");
++}
++
++
++/* ========================================================================= */
++
++#if 1
++
++/*
++ * Volatile ought to be good enough to make gcc do the right thing on S3C24xx.
++ * No need to use atomic or put barriers, keeping the code more readable.
++ *
++ * Warning: this story changes if going SMP/SMT.
++ */
++
++static volatile int masked = 1;
++static volatile int pending;
++static volatile int in_interrupt;
++
++
++static void ar6000_do_irq(struct sdio_func *func)
++{
++ HIF_DEVICE *hif = sdio_get_drvdata(func);
++ struct device *dev = HIFGetOSDevice(hif);
++ A_STATUS status;
++
++ dev_dbg(dev, "ar6000_do_irq -> %p\n", htcCallbacks.dsrHandler);
++
++ status = htcCallbacks.dsrHandler(hif->htc_handle);
++ BUG_ON(status != A_OK);
++}
++
++
++static void sdio_ar6000_irq(struct sdio_func *func)
++{
++ HIF_DEVICE *hif = sdio_get_drvdata(func);
++ struct device *dev = HIFGetOSDevice(hif);
++
++ dev_dbg(dev, "sdio_ar6000_irq\n");
++
++ in_interrupt = 1;
++ if (masked) {
++ in_interrupt = 0;
++ pending++;
++ return;
++ }
++ /*
++ * @@@ This is ugly. If we don't drop the lock, we'll deadlock when
++ * the handler tries to do SDIO. So there are four choices:
++ *
++ * 1) Break the call chain by calling the callback from a workqueue.
++ * Ugh.
++ * 2) Make process_request aware that we already have the lock.
++ * 3) Drop the lock. Which is ugly but should be safe as long as we're
++ * making sure the device doesn't go away.
++ * 4) Change the AR6k driver such that it only issues asynchronous
++ * quests when called from an interrupt.
++ *
++ * Solution 2) is probably the best for now. Will try it later.
++ */
++ sdio_release_host(func);
++ ar6000_do_irq(func);
++ sdio_claim_host(func);
++ in_interrupt = 0;
++}
++
++
++void HIFAckInterrupt(HIF_DEVICE *hif)
++{
++ struct device *dev = HIFGetOSDevice(hif);
++
++ dev_dbg(dev, "HIFAckInterrupt\n");
++ /* do nothing */
++}
++
++
++void HIFUnMaskInterrupt(HIF_DEVICE *hif)
++{
++ struct device *dev = HIFGetOSDevice(hif);
++
++ dev_dbg(dev, "HIFUnMaskInterrupt\n");
++ do {
++ masked = 1;
++ if (pending) {
++ pending = 0;
++ ar6000_do_irq(hif->func);
++ /* We may take an interrupt before unmasking and thus
++ get it pending. In this case, we just loop back. */
++ }
++ masked = 0;
++ }
++ while (pending);
++}
++
++
++void HIFMaskInterrupt(HIF_DEVICE *hif)
++{
++ struct device *dev = HIFGetOSDevice(hif);
++
++ dev_dbg(dev, "HIFMaskInterrupt\n");
++ /*
++ * Since sdio_ar6000_irq can also be called from a process context, we
++ * may conceivably end up racing with it. Thus, we need to wait until
++ * we can be sure that no concurrent interrupt processing is going on
++ * before we return.
++ *
++ * Note: this may be a bit on the paranoid side - the callers may
++ * actually be nice enough to disable scheduling. Check later.
++ */
++ masked = 1;
++ while (in_interrupt)
++ yield();
++}
++
++#endif
++
++/* ========================================================================= */
++
++/*
++ * The code below is for handling interrupts signalled out-of-band.
++ */
++#if 0
++#define IRQ_GPIO S3C2410_GPE8 /* SDDAT1 */
++
++
++static atomic_t mask = ATOMIC_INIT(1);
++
++
++static void sdio_ar6000_irq(struct sdio_func *func)
++{
++ HIF_DEVICE *hif = sdio_get_drvdata(func);
++
++ printk(KERN_DEBUG "sdio_ar6000_irq -> %p\n", htcCallbacks.dsrHandler);
++ BUG();
++}
++
++
++static void sdio_ar6000_poll(void *context)
++{
++ HIF_DEVICE *hif = context;
++ A_STATUS status;
++
++ while (1) {
++ yield();
++ if (!gpio_get_value(IRQ_GPIO))
++ continue;
++ if (!atomic_add_unless(&mask, 1, 1))
++ continue;
++ status = htcCallbacks.dsrHandler(hif->htc_handle);
++ BUG_ON(status != A_OK);
++ }
++}
++
++
++void HIFAckInterrupt(HIF_DEVICE *hif)
++{
++ struct device *dev = HIFGetOSDevice(hif);
++ int ret;
++
++ ret = atomic_dec_return(&mask);
++ BUG_ON(ret < 0);
++ dev_dbg(dev, "HIFAckInterrupt (%d)\n", ret);
++}
++
++
++void HIFUnMaskInterrupt(HIF_DEVICE *hif)
++{
++ struct device *dev = HIFGetOSDevice(hif);
++ int ret;
++
++ ret = atomic_dec_return(&mask);
++ BUG_ON(ret < 0);
++ dev_dbg(dev, "HIFUnMaskInterrupt (%d)\n", ret);
++}
++
++
++void HIFMaskInterrupt(HIF_DEVICE *hif)
++{
++ struct device *dev = HIFGetOSDevice(hif);
++ int ret;
++
++ ret = atomic_inc_return(&mask);
++ BUG_ON(ret > 1);
++ dev_dbg(dev, "HIFMaskInterrupt (%d)\n", ret);
++}
++#endif
++
++/* ========================================================================= */
++
++
++/* ----- Some stuff needed for Atheros' API -------------------------------- */
++
++struct device *HIFGetOSDevice(HIF_DEVICE *hif)
++{
++ return &hif->func->dev;
++}
++
++
++void HIFSetHandle(void *hif_handle, void *handle)
++{
++ HIF_DEVICE *hif = (HIF_DEVICE *) hif_handle;
++
++ hif->htc_handle = handle;
++}
++
++
++/* ----- */
++
++
++
++/*
++ * @@@ Atheros' HIF says this hack is necessary. Check this.
++ */
++
++static int inserter(void *data)
++{
++ int ret;
++
++ msleep(1000);
++ ret = htcCallbacks.deviceInsertedHandler(&hif_device);
++ printk(KERN_ERR "got %d\n", ret);
++ return 0;
++}
++
++
++static int sdio_ar6000_probe(struct sdio_func *func,
++ const struct sdio_device_id *id)
++{
++ struct device *dev = &func->dev;
++ int ret;
++ struct task_struct *task;
++
++ dev_dbg(dev, "sdio_ar6000_probe\n");
++ BUG_ON(!htcCallbacks.deviceInsertedHandler);
++
++ sdio_set_drvdata(func, &hif_device);
++ sdio_claim_host(func);
++ sdio_enable_func(func);
++
++ hif_device.func = func;
++ INIT_LIST_HEAD(&hif_device.queue);
++ init_waitqueue_head(&hif_device.wait);
++
++ ret = sdio_set_block_size(func, HIF_MBOX_BLOCK_SIZE);
++ if (ret < 0) {
++ dev_err(dev, "sdio_set_block_size returns %d\n", ret);
++ /* @@@ cleanup */
++ }
++ ret = sdio_claim_irq(func, sdio_ar6000_irq);
++ if (ret) {
++ dev_err(dev, "sdio_claim_irq returns %d\n", ret);
++ /* @@@ cleanup */
++ }
++#if 0 /* only for hw SDIO */
++ sdio_f0_writeb(func, SDIO_BUS_CD_DISABLE | SDIO_BUS_WIDTH_4BIT,
++ SDIO_CCCR_IF, &ret);
++ if (ret) {
++ dev_err(dev, "sdio_f0_writeb(SDIO_CCCR_IF) returns %d\n",
++ ret);
++ /* @@@ cleanup */
++ }
++#endif
++#if 0
++ sdio_f0_writeb(func, SDIO_CCCR_CAP_E4MI, SDIO_CCCR_CAPS, &ret);
++ if (ret) {
++ dev_err(dev, "sdio_f0_writeb(SDIO_CCCR_CAPS) returns %d\n",
++ ret);
++ /* @@@ cleanup */
++ }
++#endif
++ sdio_f0_writeb(func, SDIO_POWER_EMPC, SDIO_CCCR_POWER, &ret);
++ if (ret) {
++ dev_err(dev, "sdio_f0_writeb(SDIO_CCCR_POWER) returns %d\n",
++ ret);
++ /* @@@ cleanup */
++ }
++ sdio_release_host(func);
++
++ hif_device.io_task = kthread_run(io, &hif_device, "ar6000_io");
++ if (IS_ERR(hif_device.io_task)) {
++ dev_err(dev, "kthread_run(ar6000_io): %d\n", ret);
++ /* @@@ cleanup */
++ }
++ task = kthread_run(inserter, NULL, "ar6000_inserter");
++ if (IS_ERR(task)) {
++ dev_err(dev, "kthread_run (ar6000_inserter): %d\n", ret);
++ /* @@@ cleanup */
++ }
++
++ return 0;
++}
++
++
++static void sdio_ar6000_remove(struct sdio_func *func)
++{
++ sdio_claim_host(func);
++ sdio_release_irq(func);
++ sdio_disable_func(func);
++ sdio_release_host(func);
++ /* @@@ remove */
++ /* @@@ kill _tasks */
++}
++
++
++/* @@@ move these definitions to linux/mmc/sdio_ids.h */
++#define SDIO_VENDOR_ID_ATHEROS 0x271
++#define SDIO_DEVICE_ID_ATHEROS_AR6000 0x100
++
++#define ATHEROS_SDIO_DEVICE(id, offset) \
++ SDIO_DEVICE(SDIO_VENDOR_ID_ATHEROS, SDIO_DEVICE_ID_ATHEROS_##id | (offset))
++
++static const struct sdio_device_id sdio_ar6000_ids[] = {
++ { ATHEROS_SDIO_DEVICE(AR6000, 0) },
++ { ATHEROS_SDIO_DEVICE(AR6000, 0x1) },
++ { ATHEROS_SDIO_DEVICE(AR6000, 0x8) },
++ { ATHEROS_SDIO_DEVICE(AR6000, 0x9) },
++ { ATHEROS_SDIO_DEVICE(AR6000, 0xa) },
++ { ATHEROS_SDIO_DEVICE(AR6000, 0xb) },
++ { /* end: all zeroes */ },
++};
++
++MODULE_DEVICE_TABLE(sdio, sdio_ar6000_ids);
++
++
++static struct sdio_driver sdio_ar6000_driver = {
++ .probe = sdio_ar6000_probe,
++ .remove = sdio_ar6000_remove,
++ .name = "sdio_ar6000",
++ .id_table = sdio_ar6000_ids,
++};
++
++
++static int __devinit sdio_ar6000_init(void)
++{
++ printk(KERN_INFO "sdio_ar6000_init\n");
++ return sdio_register_driver(&sdio_ar6000_driver);
++}
++
++
++static void __exit sdio_ar6000_exit(void)
++{
++ printk(KERN_INFO "sdio_ar6000_exit\n");
++ sdio_unregister_driver(&sdio_ar6000_driver);
++}
++
++
++module_init(sdio_ar6000_init);
++module_exit(sdio_ar6000_exit);
++
++MODULE_AUTHOR("Werner Almesberger");
++MODULE_LICENSE("GPL");
Modified: developers/werner/wlan-spi/patches/series
===================================================================
--- developers/werner/wlan-spi/patches/series 2008-10-07 15:05:10 UTC (rev 4697)
+++ developers/werner/wlan-spi/patches/series 2008-10-07 15:07:33 UTC (rev 4698)
@@ -12,7 +12,7 @@
# rm -rf sdio ../include/linux/sdio
#
-wlan-spi-hif.patch
+hif-linux-sdio.patch
ar6k-without-sdio.patch
sdio-f0-unrestrict.patch
gta02-remove-sdio.patch
Deleted: developers/werner/wlan-spi/patches/wlan-spi-hif.patch
===================================================================
--- developers/werner/wlan-spi/patches/wlan-spi-hif.patch 2008-10-07 15:05:10 UTC (rev 4697)
+++ developers/werner/wlan-spi/patches/wlan-spi-hif.patch 2008-10-07 15:07:33 UTC (rev 4698)
@@ -1,656 +0,0 @@
-wlan-spi-hif.patch
-
-This is a replacement for Atheros' HIF layer. We use the Linux SDIO
-stack.
-
-Using Atheros' GPLv2 clause for now. Since there's only very little of
-the original code left, we should perhaps switch to GPL>=v2, which is
-preferred by Openmoko.
-
-Work in progress.
-
-Not-Yet-Signed-off-by: Werner Almesberger <werner at openmoko.org>
-
-Index: korig/drivers/ar6000/Makefile
-===================================================================
---- korig.orig/drivers/ar6000/Makefile 2008-10-07 13:00:30.000000000 -0200
-+++ korig/drivers/ar6000/Makefile 2008-10-07 13:00:32.000000000 -0200
-@@ -21,7 +21,7 @@
- htc/htc_recv.o \
- htc/htc_services.o \
- htc/htc.o \
-- hif/hif.o \
-+ hif/hif2.o \
- bmi/bmi.o \
- ar6000/ar6000_drv.o \
- ar6000/ar6000_raw_if.o \
-Index: korig/drivers/ar6000/hif/hif2.c
-===================================================================
---- /dev/null 1970-01-01 00:00:00.000000000 +0000
-+++ korig/drivers/ar6000/hif/hif2.c 2008-10-07 13:00:43.000000000 -0200
-@@ -0,0 +1,625 @@
-+/*
-+ * hif2.c - HIF layer re-implementation for the Linux SDIO stack
-+ *
-+ * Copyright (C) 2008 by OpenMoko, Inc.
-+ * Written by Werner Almesberger <werner at openmoko.org>
-+ * All Rights Reserved
-+ *
-+ * This program is free software; you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License version 2 as
-+ * published by the Free Software Foundation;
-+ *
-+ * Based on:
-+ *
-+ * @abstract: HIF layer reference implementation for Atheros SDIO stack
-+ * @notice: Copyright (c) 2004-2006 Atheros Communications Inc.
-+ */
-+
-+
-+#include <linux/kernel.h>
-+#include <linux/kthread.h>
-+#include <linux/list.h>
-+#include <linux/wait.h>
-+#include <linux/spinlock.h>
-+#include <linux/mmc/sdio_func.h>
-+#include <linux/mmc/sdio.h>
-+#include <asm/gpio.h>
-+
-+#include "athdefs.h"
-+#include "a_types.h"
-+#include "hif.h"
-+
-+
-+/*
-+ * KNOWN BUGS:
-+ *
-+ * - HIF_DEVICE_IRQ_ASYNC_SYNC doesn't work yet (gets MMC errors)
-+ * - does not work with S3C SDI (probably due to bugs in the latter)
-+ * - not ready for S3C SPI yet
-+ * - no cleanup on setup errors yet
-+ * - driver doesn't remove cleanly yet
-+ * - latency can reach hundreds of ms, probably because of scheduling delays
-+ * - packets go through about three queues before finally hitting the network
-+ */
-+
-+
-+//#define dev_dbg dev_info
-+#define dev_dbg(dev, ...) ((void) dev)
-+
-+#define MBOXES 4
-+
-+#define HIF_MBOX_BLOCK_SIZE 128
-+#define HIF_MBOX_BASE_ADDR 0x800
-+#define HIF_MBOX_WIDTH 0x800
-+#define HIF_MBOX_START_ADDR(mbox) \
-+ (HIF_MBOX_BASE_ADDR+(mbox)*HIF_MBOX_WIDTH)
-+
-+
-+struct hif_device {
-+ void *htc_handle;
-+ struct sdio_func *func;
-+
-+ /*
-+ * @@@ our sweet little bit of bogosity - the mechanism that lets us
-+ * use the SDIO stack from softirqs. This really wants to use skbs.
-+ */
-+ struct list_head queue;
-+ spinlock_t queue_lock;
-+ struct task_struct *io_task;
-+ wait_queue_head_t wait;
-+};
-+
-+struct hif_request {
-+ struct list_head list;
-+ struct sdio_func *func;
-+ int (*read)(struct sdio_func *func,
-+ void *dst, unsigned int addr, int count);
-+ int (*write)(struct sdio_func *func,
-+ unsigned int addr, void *src, int count);
-+ void *buf;
-+ unsigned long addr;
-+ int len;
-+ A_STATUS (*completion)(void *context, A_STATUS status);
-+ void *context;
-+};
-+
-+
-+static HIF_DEVICE hif_device;
-+static HTC_CALLBACKS htcCallbacks;
-+
-+
-+int HIFInit(HTC_CALLBACKS *callbacks)
-+{
-+ BUG_ON(!callbacks);
-+
-+ printk(KERN_INFO "HIFInit\n");
-+ htcCallbacks = *callbacks;
-+
-+ return 0;
-+}
-+
-+
-+static A_STATUS process_request(struct hif_request *req)
-+{
-+ int ret;
-+ A_STATUS status;
-+
-+ dev_dbg(&req->func->dev, "process_request(req %p)\n", req);
-+ sdio_claim_host(req->func);
-+ if (req->read)
-+ ret = req->read(req->func, req->buf, req->addr, req->len);
-+ else
-+ ret = req->write(req->func, req->addr, req->buf, req->len);
-+ sdio_release_host(req->func);
-+ status = ret ? A_ERROR : A_OK;
-+ if (req->completion)
-+ req->completion(req->context, status);
-+ kfree(req);
-+ return status;
-+}
-+
-+
-+static void enqueue_request(struct hif_device *hif, struct hif_request *req)
-+{
-+ unsigned long flags;
-+
-+ dev_dbg(&req->func->dev, "enqueue_request(req %p)\n", req);
-+ spin_lock_irqsave(&hif->queue_lock, flags);
-+ list_add_tail(&req->list, &hif->queue);
-+ spin_unlock_irqrestore(&hif->queue_lock, flags);
-+ wake_up(&hif->wait);
-+}
-+
-+
-+static struct hif_request *dequeue_request(struct hif_device *hif)
-+{
-+ struct hif_request *req;
-+ unsigned long flags;
-+
-+ spin_lock_irqsave(&hif->queue_lock, flags);
-+ if (list_empty(&hif->queue))
-+ req = NULL;
-+ else {
-+ req = list_first_entry(&hif->queue,
-+ struct hif_request, list);
-+ list_del(&req->list);
-+ }
-+ spin_unlock_irqrestore(&hif->queue_lock, flags);
-+ return req;
-+}
-+
-+
-+static int io(void *data)
-+{
-+ struct hif_device *hif = data;
-+ DECLARE_WAITQUEUE(wait, current);
-+ struct hif_request *req;
-+
-+ while (1) {
-+ add_wait_queue(&hif->wait, &wait);
-+ while (1) {
-+ set_current_state(TASK_INTERRUPTIBLE);
-+ if (signal_pending(current)) {
-+ req = NULL;
-+ break;
-+ }
-+ req = dequeue_request(hif);
-+ if (req)
-+ break;
-+ schedule();
-+ }
-+ set_current_state(TASK_RUNNING);
-+ remove_wait_queue(&hif->wait, &wait);
-+
-+ if (!req)
-+ break;
-+
-+ (void) process_request(req);
-+ }
-+ return 0;
-+}
-+
-+
-+A_STATUS HIFReadWrite(HIF_DEVICE *hif, A_UINT32 address, A_UCHAR *buffer,
-+ A_UINT32 length, A_UINT32 request, void *context)
-+{
-+ struct device *dev = HIFGetOSDevice(hif);
-+ struct hif_request *req;
-+
-+ dev_dbg(dev, "HIFReadWrite(device %p, address 0x%x, buffer %p, "
-+ "length %d, request 0x%x, context %p)\n",
-+ hif, address, buffer, length, request, context);
-+
-+ BUG_ON(!(request & (HIF_SYNCHRONOUS | HIF_ASYNCHRONOUS)));
-+ BUG_ON(!(request & (HIF_BYTE_BASIS | HIF_BLOCK_BASIS)));
-+ BUG_ON(!(request & (HIF_READ | HIF_WRITE)));
-+ BUG_ON(!(request & HIF_EXTENDED_IO));
-+
-+ if (address >= HIF_MBOX_START_ADDR(0) &&
-+ address < HIF_MBOX_START_ADDR(MBOXES+1)) {
-+ BUG_ON(length > HIF_MBOX_WIDTH);
-+ /* Adjust the address so that the last byte falls on the EOM
-+ address. */
-+ address += HIF_MBOX_WIDTH-length;
-+ }
-+
-+ req = kzalloc(sizeof(*req), GFP_ATOMIC);
-+ if (!req) {
-+ if (request & HIF_ASYNCHRONOUS)
-+ htcCallbacks.rwCompletionHandler(context, A_ERROR);
-+ return A_ERROR;
-+ }
-+
-+ req->func = hif->func;
-+ req->addr = address;
-+ req->buf = buffer;
-+ req->len = length;
-+
-+ if (request & HIF_READ) {
-+ if (request & HIF_FIXED_ADDRESS)
-+ req->read = sdio_readsb;
-+ else
-+ req->read = sdio_memcpy_fromio;
-+ } else {
-+ if (request & HIF_FIXED_ADDRESS)
-+ req->write = sdio_writesb;
-+ else
-+ req->write = sdio_memcpy_toio;
-+ }
-+
-+ if (!(request & HIF_ASYNCHRONOUS))
-+ return process_request(req);
-+
-+ req->completion = htcCallbacks.rwCompletionHandler;
-+ req->context = context;
-+ enqueue_request(hif, req);
-+
-+ return A_OK;
-+}
-+
-+
-+A_STATUS HIFConfigureDevice(HIF_DEVICE *hif,
-+ HIF_DEVICE_CONFIG_OPCODE opcode, void *config, A_UINT32 configLen)
-+{
-+ struct device *dev = HIFGetOSDevice(hif);
-+ HIF_DEVICE_IRQ_PROCESSING_MODE *ipm_cfg = config;
-+ A_UINT32 *mbs_cfg = config;
-+ int i;
-+
-+ dev_dbg(dev, "HIFConfigureDevice\n");
-+
-+ switch (opcode) {
-+ case HIF_DEVICE_GET_MBOX_BLOCK_SIZE:
-+ for (i = 0; i != MBOXES; i++)
-+ mbs_cfg[i] = HIF_MBOX_BLOCK_SIZE;
-+ break;
-+ case HIF_DEVICE_GET_MBOX_ADDR:
-+ for (i = 0; i != MBOXES; i++)
-+ mbs_cfg[i] = HIF_MBOX_START_ADDR(i);
-+ break;
-+ case HIF_DEVICE_GET_IRQ_PROC_MODE:
-+ *ipm_cfg = HIF_DEVICE_IRQ_SYNC_ONLY;
-+// *ipm_cfg = HIF_DEVICE_IRQ_ASYNC_SYNC;
-+ break;
-+ default:
-+ return A_ERROR;
-+ }
-+ return A_OK;
-+}
-+
-+
-+void HIFShutDownDevice(HIF_DEVICE *hif)
-+{
-+ struct device *dev = HIFGetOSDevice(hif);
-+
-+ dev_dbg(dev, "HIFShutDownDevice\n");
-+}
-+
-+
-+/* ========================================================================= */
-+
-+#if 1
-+
-+/*
-+ * Volatile ought to be good enough to make gcc do the right thing on S3C24xx.
-+ * No need to use atomic or put barriers, keeping the code more readable.
-+ *
-+ * Warning: this story changes if going SMP/SMT.
-+ */
-+
-+static volatile int masked = 1;
-+static volatile int pending;
-+static volatile int in_interrupt;
-+
-+
-+static void ar6000_do_irq(struct sdio_func *func)
-+{
-+ HIF_DEVICE *hif = sdio_get_drvdata(func);
-+ struct device *dev = HIFGetOSDevice(hif);
-+ A_STATUS status;
-+
-+ dev_dbg(dev, "ar6000_do_irq -> %p\n", htcCallbacks.dsrHandler);
-+
-+ status = htcCallbacks.dsrHandler(hif->htc_handle);
-+ BUG_ON(status != A_OK);
-+}
-+
-+
-+static void sdio_ar6000_irq(struct sdio_func *func)
-+{
-+ HIF_DEVICE *hif = sdio_get_drvdata(func);
-+ struct device *dev = HIFGetOSDevice(hif);
-+
-+ dev_dbg(dev, "sdio_ar6000_irq\n");
-+
-+ in_interrupt = 1;
-+ if (masked) {
-+ in_interrupt = 0;
-+ pending++;
-+ return;
-+ }
-+ /*
-+ * @@@ This is ugly. If we don't drop the lock, we'll deadlock when
-+ * the handler tries to do SDIO. So there are four choices:
-+ *
-+ * 1) Break the call chain by calling the callback from a workqueue.
-+ * Ugh.
-+ * 2) Make process_request aware that we already have the lock.
-+ * 3) Drop the lock. Which is ugly but should be safe as long as we're
-+ * making sure the device doesn't go away.
-+ * 4) Change the AR6k driver such that it only issues asynchronous
-+ * quests when called from an interrupt.
-+ *
-+ * Solution 2) is probably the best for now. Will try it later.
-+ */
-+ sdio_release_host(func);
-+ ar6000_do_irq(func);
-+ sdio_claim_host(func);
-+ in_interrupt = 0;
-+}
-+
-+
-+void HIFAckInterrupt(HIF_DEVICE *hif)
-+{
-+ struct device *dev = HIFGetOSDevice(hif);
-+
-+ dev_dbg(dev, "HIFAckInterrupt\n");
-+ /* do nothing */
-+}
-+
-+
-+void HIFUnMaskInterrupt(HIF_DEVICE *hif)
-+{
-+ struct device *dev = HIFGetOSDevice(hif);
-+
-+ dev_dbg(dev, "HIFUnMaskInterrupt\n");
-+ do {
-+ masked = 1;
-+ if (pending) {
-+ pending = 0;
-+ ar6000_do_irq(hif->func);
-+ /* We may take an interrupt before unmasking and thus
-+ get it pending. In this case, we just loop back. */
-+ }
-+ masked = 0;
-+ }
-+ while (pending);
-+}
-+
-+
-+void HIFMaskInterrupt(HIF_DEVICE *hif)
-+{
-+ struct device *dev = HIFGetOSDevice(hif);
-+
-+ dev_dbg(dev, "HIFMaskInterrupt\n");
-+ /*
-+ * Since sdio_ar6000_irq can also be called from a process context, we
-+ * may conceivably end up racing with it. Thus, we need to wait until
-+ * we can be sure that no concurrent interrupt processing is going on
-+ * before we return.
-+ *
-+ * Note: this may be a bit on the paranoid side - the callers may
-+ * actually be nice enough to disable scheduling. Check later.
-+ */
-+ masked = 1;
-+ while (in_interrupt)
-+ yield();
-+}
-+
-+#endif
-+
-+/* ========================================================================= */
-+
-+/*
-+ * The code below is for handling interrupts signalled out-of-band.
-+ */
-+#if 0
-+#define IRQ_GPIO S3C2410_GPE8 /* SDDAT1 */
-+
-+
-+static atomic_t mask = ATOMIC_INIT(1);
-+
-+
-+static void sdio_ar6000_irq(struct sdio_func *func)
-+{
-+ HIF_DEVICE *hif = sdio_get_drvdata(func);
-+
-+ printk(KERN_DEBUG "sdio_ar6000_irq -> %p\n", htcCallbacks.dsrHandler);
-+ BUG();
-+}
-+
-+
-+static void sdio_ar6000_poll(void *context)
-+{
-+ HIF_DEVICE *hif = context;
-+ A_STATUS status;
-+
-+ while (1) {
-+ yield();
-+ if (!gpio_get_value(IRQ_GPIO))
-+ continue;
-+ if (!atomic_add_unless(&mask, 1, 1))
-+ continue;
-+ status = htcCallbacks.dsrHandler(hif->htc_handle);
-+ BUG_ON(status != A_OK);
-+ }
-+}
-+
-+
-+void HIFAckInterrupt(HIF_DEVICE *hif)
-+{
-+ struct device *dev = HIFGetOSDevice(hif);
-+ int ret;
-+
-+ ret = atomic_dec_return(&mask);
-+ BUG_ON(ret < 0);
-+ dev_dbg(dev, "HIFAckInterrupt (%d)\n", ret);
-+}
-+
-+
-+void HIFUnMaskInterrupt(HIF_DEVICE *hif)
-+{
-+ struct device *dev = HIFGetOSDevice(hif);
-+ int ret;
-+
-+ ret = atomic_dec_return(&mask);
-+ BUG_ON(ret < 0);
-+ dev_dbg(dev, "HIFUnMaskInterrupt (%d)\n", ret);
-+}
-+
-+
-+void HIFMaskInterrupt(HIF_DEVICE *hif)
-+{
-+ struct device *dev = HIFGetOSDevice(hif);
-+ int ret;
-+
-+ ret = atomic_inc_return(&mask);
-+ BUG_ON(ret > 1);
-+ dev_dbg(dev, "HIFMaskInterrupt (%d)\n", ret);
-+}
-+#endif
-+
-+/* ========================================================================= */
-+
-+
-+/* ----- Some stuff needed for Atheros' API -------------------------------- */
-+
-+struct device *HIFGetOSDevice(HIF_DEVICE *hif)
-+{
-+ return &hif->func->dev;
-+}
-+
-+
-+void HIFSetHandle(void *hif_handle, void *handle)
-+{
-+ HIF_DEVICE *hif = (HIF_DEVICE *) hif_handle;
-+
-+ hif->htc_handle = handle;
-+}
-+
-+
-+/* ----- */
-+
-+
-+
-+/*
-+ * @@@ Atheros' HIF says this hack is necessary. Check this.
-+ */
-+
-+static int inserter(void *data)
-+{
-+ int ret;
-+
-+ msleep(1000);
-+ ret = htcCallbacks.deviceInsertedHandler(&hif_device);
-+ printk(KERN_ERR "got %d\n", ret);
-+ return 0;
-+}
-+
-+
-+static int sdio_ar6000_probe(struct sdio_func *func,
-+ const struct sdio_device_id *id)
-+{
-+ struct device *dev = &func->dev;
-+ int ret;
-+ struct task_struct *task;
-+
-+ dev_dbg(dev, "sdio_ar6000_probe\n");
-+ BUG_ON(!htcCallbacks.deviceInsertedHandler);
-+
-+ sdio_set_drvdata(func, &hif_device);
-+ sdio_claim_host(func);
-+ sdio_enable_func(func);
-+
-+ hif_device.func = func;
-+ INIT_LIST_HEAD(&hif_device.queue);
-+ init_waitqueue_head(&hif_device.wait);
-+
-+ ret = sdio_set_block_size(func, HIF_MBOX_BLOCK_SIZE);
-+ if (ret < 0) {
-+ dev_err(dev, "sdio_set_block_size returns %d\n", ret);
-+ /* @@@ cleanup */
-+ }
-+ ret = sdio_claim_irq(func, sdio_ar6000_irq);
-+ if (ret) {
-+ dev_err(dev, "sdio_claim_irq returns %d\n", ret);
-+ /* @@@ cleanup */
-+ }
-+#if 0 /* only for hw SDIO */
-+ sdio_f0_writeb(func, SDIO_BUS_CD_DISABLE | SDIO_BUS_WIDTH_4BIT,
-+ SDIO_CCCR_IF, &ret);
-+ if (ret) {
-+ dev_err(dev, "sdio_f0_writeb(SDIO_CCCR_IF) returns %d\n",
-+ ret);
-+ /* @@@ cleanup */
-+ }
-+#endif
-+#if 0
-+ sdio_f0_writeb(func, SDIO_CCCR_CAP_E4MI, SDIO_CCCR_CAPS, &ret);
-+ if (ret) {
-+ dev_err(dev, "sdio_f0_writeb(SDIO_CCCR_CAPS) returns %d\n",
-+ ret);
-+ /* @@@ cleanup */
-+ }
-+#endif
-+ sdio_f0_writeb(func, SDIO_POWER_EMPC, SDIO_CCCR_POWER, &ret);
-+ if (ret) {
-+ dev_err(dev, "sdio_f0_writeb(SDIO_CCCR_POWER) returns %d\n",
-+ ret);
-+ /* @@@ cleanup */
-+ }
-+ sdio_release_host(func);
-+
-+ hif_device.io_task = kthread_run(io, &hif_device, "ar6000_io");
-+ if (IS_ERR(hif_device.io_task)) {
-+ dev_err(dev, "kthread_run(ar6000_io): %d\n", ret);
-+ /* @@@ cleanup */
-+ }
-+ task = kthread_run(inserter, NULL, "ar6000_inserter");
-+ if (IS_ERR(task)) {
-+ dev_err(dev, "kthread_run (ar6000_inserter): %d\n", ret);
-+ /* @@@ cleanup */
-+ }
-+
-+ return 0;
-+}
-+
-+
-+static void sdio_ar6000_remove(struct sdio_func *func)
-+{
-+ sdio_claim_host(func);
-+ sdio_release_irq(func);
-+ sdio_disable_func(func);
-+ sdio_release_host(func);
-+ /* @@@ remove */
-+ /* @@@ kill _tasks */
-+}
-+
-+
-+/* @@@ move these definitions to linux/mmc/sdio_ids.h */
-+#define SDIO_VENDOR_ID_ATHEROS 0x271
-+#define SDIO_DEVICE_ID_ATHEROS_AR6000 0x100
-+
-+#define ATHEROS_SDIO_DEVICE(id, offset) \
-+ SDIO_DEVICE(SDIO_VENDOR_ID_ATHEROS, SDIO_DEVICE_ID_ATHEROS_##id | (offset))
-+
-+static const struct sdio_device_id sdio_ar6000_ids[] = {
-+ { ATHEROS_SDIO_DEVICE(AR6000, 0) },
-+ { ATHEROS_SDIO_DEVICE(AR6000, 0x1) },
-+ { ATHEROS_SDIO_DEVICE(AR6000, 0x8) },
-+ { ATHEROS_SDIO_DEVICE(AR6000, 0x9) },
-+ { ATHEROS_SDIO_DEVICE(AR6000, 0xa) },
-+ { ATHEROS_SDIO_DEVICE(AR6000, 0xb) },
-+ { /* end: all zeroes */ },
-+};
-+
-+MODULE_DEVICE_TABLE(sdio, sdio_ar6000_ids);
-+
-+
-+static struct sdio_driver sdio_ar6000_driver = {
-+ .probe = sdio_ar6000_probe,
-+ .remove = sdio_ar6000_remove,
-+ .name = "sdio_ar6000",
-+ .id_table = sdio_ar6000_ids,
-+};
-+
-+
-+static int __devinit sdio_ar6000_init(void)
-+{
-+ printk(KERN_INFO "sdio_ar6000_init\n");
-+ return sdio_register_driver(&sdio_ar6000_driver);
-+}
-+
-+
-+static void __exit sdio_ar6000_exit(void)
-+{
-+ printk(KERN_INFO "sdio_ar6000_exit\n");
-+ sdio_unregister_driver(&sdio_ar6000_driver);
-+}
-+
-+
-+module_init(sdio_ar6000_init);
-+module_exit(sdio_ar6000_exit);
-+
-+MODULE_AUTHOR("Werner Almesberger");
-+MODULE_LICENSE("GPL");
More information about the commitlog
mailing list