|
@@ -0,0 +1,804 @@
|
|
|
+#include <linux/delay.h>
|
|
|
+#include <linux/device.h>
|
|
|
+#include <linux/dmapool.h>
|
|
|
+#include <linux/dma-mapping.h>
|
|
|
+#include <linux/init.h>
|
|
|
+#include <linux/platform_device.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/interrupt.h>
|
|
|
+#include <linux/io.h>
|
|
|
+#include <linux/irq.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/slab.h>
|
|
|
+#include <linux/pm_runtime.h>
|
|
|
+#include <linux/usb/ch9.h>
|
|
|
+#include <linux/usb/gadget.h>
|
|
|
+#include <linux/usb/otg.h>
|
|
|
+#include <linux/usb/chipidea.h>
|
|
|
+
|
|
|
+#include "ci.h"
|
|
|
+#include "udc.h"
|
|
|
+#include "bits.h"
|
|
|
+#include "debug.h"
|
|
|
+
|
|
|
+/* Interrupt statistics */
|
|
|
+#define ISR_MASK 0x1F
|
|
|
+static struct isr_statistics {
|
|
|
+ u32 test;
|
|
|
+ u32 ui;
|
|
|
+ u32 uei;
|
|
|
+ u32 pci;
|
|
|
+ u32 uri;
|
|
|
+ u32 sli;
|
|
|
+ u32 none;
|
|
|
+ struct {
|
|
|
+ u32 cnt;
|
|
|
+ u32 buf[ISR_MASK+1];
|
|
|
+ u32 idx;
|
|
|
+ } hndl;
|
|
|
+} isr_statistics;
|
|
|
+
|
|
|
+void dbg_interrupt(u32 intmask)
|
|
|
+{
|
|
|
+ if (!intmask) {
|
|
|
+ isr_statistics.none++;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ isr_statistics.hndl.buf[isr_statistics.hndl.idx++] = intmask;
|
|
|
+ isr_statistics.hndl.idx &= ISR_MASK;
|
|
|
+ isr_statistics.hndl.cnt++;
|
|
|
+
|
|
|
+ if (USBi_URI & intmask)
|
|
|
+ isr_statistics.uri++;
|
|
|
+ if (USBi_PCI & intmask)
|
|
|
+ isr_statistics.pci++;
|
|
|
+ if (USBi_UEI & intmask)
|
|
|
+ isr_statistics.uei++;
|
|
|
+ if (USBi_UI & intmask)
|
|
|
+ isr_statistics.ui++;
|
|
|
+ if (USBi_SLI & intmask)
|
|
|
+ isr_statistics.sli++;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hw_register_read: reads all device registers (execute without interruption)
|
|
|
+ * @buf: destination buffer
|
|
|
+ * @size: buffer size
|
|
|
+ *
|
|
|
+ * This function returns number of registers read
|
|
|
+ */
|
|
|
+static size_t hw_register_read(struct ci13xxx *udc, u32 *buf, size_t size)
|
|
|
+{
|
|
|
+ unsigned i;
|
|
|
+
|
|
|
+ if (size > udc->hw_bank.size)
|
|
|
+ size = udc->hw_bank.size;
|
|
|
+
|
|
|
+ for (i = 0; i < size; i++)
|
|
|
+ buf[i] = hw_read(udc, i * sizeof(u32), ~0);
|
|
|
+
|
|
|
+ return size;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hw_register_write: writes to register
|
|
|
+ * @addr: register address
|
|
|
+ * @data: register value
|
|
|
+ *
|
|
|
+ * This function returns an error code
|
|
|
+ */
|
|
|
+static int hw_register_write(struct ci13xxx *udc, u16 addr, u32 data)
|
|
|
+{
|
|
|
+ /* align */
|
|
|
+ addr /= sizeof(u32);
|
|
|
+
|
|
|
+ if (addr >= udc->hw_bank.size)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* align */
|
|
|
+ addr *= sizeof(u32);
|
|
|
+
|
|
|
+ hw_write(udc, addr, ~0, data);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hw_intr_clear: disables interrupt & clears interrupt status (execute without
|
|
|
+ * interruption)
|
|
|
+ * @n: interrupt bit
|
|
|
+ *
|
|
|
+ * This function returns an error code
|
|
|
+ */
|
|
|
+static int hw_intr_clear(struct ci13xxx *udc, int n)
|
|
|
+{
|
|
|
+ if (n >= REG_BITS)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ hw_write(udc, OP_USBINTR, BIT(n), 0);
|
|
|
+ hw_write(udc, OP_USBSTS, BIT(n), BIT(n));
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hw_intr_force: enables interrupt & forces interrupt status (execute without
|
|
|
+ * interruption)
|
|
|
+ * @n: interrupt bit
|
|
|
+ *
|
|
|
+ * This function returns an error code
|
|
|
+ */
|
|
|
+static int hw_intr_force(struct ci13xxx *udc, int n)
|
|
|
+{
|
|
|
+ if (n >= REG_BITS)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ hw_write(udc, CAP_TESTMODE, TESTMODE_FORCE, TESTMODE_FORCE);
|
|
|
+ hw_write(udc, OP_USBINTR, BIT(n), BIT(n));
|
|
|
+ hw_write(udc, OP_USBSTS, BIT(n), BIT(n));
|
|
|
+ hw_write(udc, CAP_TESTMODE, TESTMODE_FORCE, 0);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * show_device: prints information about device capabilities and status
|
|
|
+ *
|
|
|
+ * Check "device.h" for details
|
|
|
+ */
|
|
|
+static ssize_t show_device(struct device *dev, struct device_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
|
|
|
+ struct usb_gadget *gadget = &udc->gadget;
|
|
|
+ int n = 0;
|
|
|
+
|
|
|
+ if (attr == NULL || buf == NULL) {
|
|
|
+ dev_err(udc->dev, "[%s] EINVAL\n", __func__);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "speed = %d\n",
|
|
|
+ gadget->speed);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "max_speed = %d\n",
|
|
|
+ gadget->max_speed);
|
|
|
+ /* TODO: Scheduled for removal in 3.8. */
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "is_dualspeed = %d\n",
|
|
|
+ gadget_is_dualspeed(gadget));
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "is_otg = %d\n",
|
|
|
+ gadget->is_otg);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "is_a_peripheral = %d\n",
|
|
|
+ gadget->is_a_peripheral);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "b_hnp_enable = %d\n",
|
|
|
+ gadget->b_hnp_enable);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "a_hnp_support = %d\n",
|
|
|
+ gadget->a_hnp_support);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "a_alt_hnp_support = %d\n",
|
|
|
+ gadget->a_alt_hnp_support);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "name = %s\n",
|
|
|
+ (gadget->name ? gadget->name : ""));
|
|
|
+
|
|
|
+ return n;
|
|
|
+}
|
|
|
+static DEVICE_ATTR(device, S_IRUSR, show_device, NULL);
|
|
|
+
|
|
|
+/**
|
|
|
+ * show_driver: prints information about attached gadget (if any)
|
|
|
+ *
|
|
|
+ * Check "device.h" for details
|
|
|
+ */
|
|
|
+static ssize_t show_driver(struct device *dev, struct device_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
|
|
|
+ struct usb_gadget_driver *driver = udc->driver;
|
|
|
+ int n = 0;
|
|
|
+
|
|
|
+ if (attr == NULL || buf == NULL) {
|
|
|
+ dev_err(dev, "[%s] EINVAL\n", __func__);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (driver == NULL)
|
|
|
+ return scnprintf(buf, PAGE_SIZE,
|
|
|
+ "There is no gadget attached!\n");
|
|
|
+
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "function = %s\n",
|
|
|
+ (driver->function ? driver->function : ""));
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "max speed = %d\n",
|
|
|
+ driver->max_speed);
|
|
|
+
|
|
|
+ return n;
|
|
|
+}
|
|
|
+static DEVICE_ATTR(driver, S_IRUSR, show_driver, NULL);
|
|
|
+
|
|
|
+/* Maximum event message length */
|
|
|
+#define DBG_DATA_MSG 64UL
|
|
|
+
|
|
|
+/* Maximum event messages */
|
|
|
+#define DBG_DATA_MAX 128UL
|
|
|
+
|
|
|
+/* Event buffer descriptor */
|
|
|
+static struct {
|
|
|
+ char (buf[DBG_DATA_MAX])[DBG_DATA_MSG]; /* buffer */
|
|
|
+ unsigned idx; /* index */
|
|
|
+ unsigned tty; /* print to console? */
|
|
|
+ rwlock_t lck; /* lock */
|
|
|
+} dbg_data = {
|
|
|
+ .idx = 0,
|
|
|
+ .tty = 0,
|
|
|
+ .lck = __RW_LOCK_UNLOCKED(lck)
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * dbg_dec: decrements debug event index
|
|
|
+ * @idx: buffer index
|
|
|
+ */
|
|
|
+static void dbg_dec(unsigned *idx)
|
|
|
+{
|
|
|
+ *idx = (*idx - 1) & (DBG_DATA_MAX-1);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dbg_inc: increments debug event index
|
|
|
+ * @idx: buffer index
|
|
|
+ */
|
|
|
+static void dbg_inc(unsigned *idx)
|
|
|
+{
|
|
|
+ *idx = (*idx + 1) & (DBG_DATA_MAX-1);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dbg_print: prints the common part of the event
|
|
|
+ * @addr: endpoint address
|
|
|
+ * @name: event name
|
|
|
+ * @status: status
|
|
|
+ * @extra: extra information
|
|
|
+ */
|
|
|
+static void dbg_print(u8 addr, const char *name, int status, const char *extra)
|
|
|
+{
|
|
|
+ struct timeval tval;
|
|
|
+ unsigned int stamp;
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ write_lock_irqsave(&dbg_data.lck, flags);
|
|
|
+
|
|
|
+ do_gettimeofday(&tval);
|
|
|
+ stamp = tval.tv_sec & 0xFFFF; /* 2^32 = 4294967296. Limit to 4096s */
|
|
|
+ stamp = stamp * 1000000 + tval.tv_usec;
|
|
|
+
|
|
|
+ scnprintf(dbg_data.buf[dbg_data.idx], DBG_DATA_MSG,
|
|
|
+ "%04X\t? %02X %-7.7s %4i ?\t%s\n",
|
|
|
+ stamp, addr, name, status, extra);
|
|
|
+
|
|
|
+ dbg_inc(&dbg_data.idx);
|
|
|
+
|
|
|
+ write_unlock_irqrestore(&dbg_data.lck, flags);
|
|
|
+
|
|
|
+ if (dbg_data.tty != 0)
|
|
|
+ pr_notice("%04X\t? %02X %-7.7s %4i ?\t%s\n",
|
|
|
+ stamp, addr, name, status, extra);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dbg_done: prints a DONE event
|
|
|
+ * @addr: endpoint address
|
|
|
+ * @td: transfer descriptor
|
|
|
+ * @status: status
|
|
|
+ */
|
|
|
+void dbg_done(u8 addr, const u32 token, int status)
|
|
|
+{
|
|
|
+ char msg[DBG_DATA_MSG];
|
|
|
+
|
|
|
+ scnprintf(msg, sizeof(msg), "%d %02X",
|
|
|
+ (int)(token & TD_TOTAL_BYTES) >> ffs_nr(TD_TOTAL_BYTES),
|
|
|
+ (int)(token & TD_STATUS) >> ffs_nr(TD_STATUS));
|
|
|
+ dbg_print(addr, "DONE", status, msg);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dbg_event: prints a generic event
|
|
|
+ * @addr: endpoint address
|
|
|
+ * @name: event name
|
|
|
+ * @status: status
|
|
|
+ */
|
|
|
+void dbg_event(u8 addr, const char *name, int status)
|
|
|
+{
|
|
|
+ if (name != NULL)
|
|
|
+ dbg_print(addr, name, status, "");
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * dbg_queue: prints a QUEUE event
|
|
|
+ * @addr: endpoint address
|
|
|
+ * @req: USB request
|
|
|
+ * @status: status
|
|
|
+ */
|
|
|
+void dbg_queue(u8 addr, const struct usb_request *req, int status)
|
|
|
+{
|
|
|
+ char msg[DBG_DATA_MSG];
|
|
|
+
|
|
|
+ if (req != NULL) {
|
|
|
+ scnprintf(msg, sizeof(msg),
|
|
|
+ "%d %d", !req->no_interrupt, req->length);
|
|
|
+ dbg_print(addr, "QUEUE", status, msg);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dbg_setup: prints a SETUP event
|
|
|
+ * @addr: endpoint address
|
|
|
+ * @req: setup request
|
|
|
+ */
|
|
|
+void dbg_setup(u8 addr, const struct usb_ctrlrequest *req)
|
|
|
+{
|
|
|
+ char msg[DBG_DATA_MSG];
|
|
|
+
|
|
|
+ if (req != NULL) {
|
|
|
+ scnprintf(msg, sizeof(msg),
|
|
|
+ "%02X %02X %04X %04X %d", req->bRequestType,
|
|
|
+ req->bRequest, le16_to_cpu(req->wValue),
|
|
|
+ le16_to_cpu(req->wIndex), le16_to_cpu(req->wLength));
|
|
|
+ dbg_print(addr, "SETUP", 0, msg);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * show_events: displays the event buffer
|
|
|
+ *
|
|
|
+ * Check "device.h" for details
|
|
|
+ */
|
|
|
+static ssize_t show_events(struct device *dev, struct device_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+ unsigned i, j, n = 0;
|
|
|
+
|
|
|
+ if (attr == NULL || buf == NULL) {
|
|
|
+ dev_err(dev->parent, "[%s] EINVAL\n", __func__);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ read_lock_irqsave(&dbg_data.lck, flags);
|
|
|
+
|
|
|
+ i = dbg_data.idx;
|
|
|
+ for (dbg_dec(&i); i != dbg_data.idx; dbg_dec(&i)) {
|
|
|
+ n += strlen(dbg_data.buf[i]);
|
|
|
+ if (n >= PAGE_SIZE) {
|
|
|
+ n -= strlen(dbg_data.buf[i]);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for (j = 0, dbg_inc(&i); j < n; dbg_inc(&i))
|
|
|
+ j += scnprintf(buf + j, PAGE_SIZE - j,
|
|
|
+ "%s", dbg_data.buf[i]);
|
|
|
+
|
|
|
+ read_unlock_irqrestore(&dbg_data.lck, flags);
|
|
|
+
|
|
|
+ return n;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * store_events: configure if events are going to be also printed to console
|
|
|
+ *
|
|
|
+ * Check "device.h" for details
|
|
|
+ */
|
|
|
+static ssize_t store_events(struct device *dev, struct device_attribute *attr,
|
|
|
+ const char *buf, size_t count)
|
|
|
+{
|
|
|
+ unsigned tty;
|
|
|
+
|
|
|
+ if (attr == NULL || buf == NULL) {
|
|
|
+ dev_err(dev, "[%s] EINVAL\n", __func__);
|
|
|
+ goto done;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sscanf(buf, "%u", &tty) != 1 || tty > 1) {
|
|
|
+ dev_err(dev, "<1|0>: enable|disable console log\n");
|
|
|
+ goto done;
|
|
|
+ }
|
|
|
+
|
|
|
+ dbg_data.tty = tty;
|
|
|
+ dev_info(dev, "tty = %u", dbg_data.tty);
|
|
|
+
|
|
|
+ done:
|
|
|
+ return count;
|
|
|
+}
|
|
|
+static DEVICE_ATTR(events, S_IRUSR | S_IWUSR, show_events, store_events);
|
|
|
+
|
|
|
+/**
|
|
|
+ * show_inters: interrupt status, enable status and historic
|
|
|
+ *
|
|
|
+ * Check "device.h" for details
|
|
|
+ */
|
|
|
+static ssize_t show_inters(struct device *dev, struct device_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
|
|
|
+ unsigned long flags;
|
|
|
+ u32 intr;
|
|
|
+ unsigned i, j, n = 0;
|
|
|
+
|
|
|
+ if (attr == NULL || buf == NULL) {
|
|
|
+ dev_err(udc->dev, "[%s] EINVAL\n", __func__);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_lock_irqsave(&udc->lock, flags);
|
|
|
+
|
|
|
+ /*n += scnprintf(buf + n, PAGE_SIZE - n,
|
|
|
+ "status = %08x\n", hw_read_intr_status(udc));
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n,
|
|
|
+ "enable = %08x\n", hw_read_intr_enable(udc));*/
|
|
|
+
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "*test = %d\n",
|
|
|
+ isr_statistics.test);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "? ui = %d\n",
|
|
|
+ isr_statistics.ui);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "? uei = %d\n",
|
|
|
+ isr_statistics.uei);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "? pci = %d\n",
|
|
|
+ isr_statistics.pci);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "? uri = %d\n",
|
|
|
+ isr_statistics.uri);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "? sli = %d\n",
|
|
|
+ isr_statistics.sli);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "*none = %d\n",
|
|
|
+ isr_statistics.none);
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "*hndl = %d\n",
|
|
|
+ isr_statistics.hndl.cnt);
|
|
|
+
|
|
|
+ for (i = isr_statistics.hndl.idx, j = 0; j <= ISR_MASK; j++, i++) {
|
|
|
+ i &= ISR_MASK;
|
|
|
+ intr = isr_statistics.hndl.buf[i];
|
|
|
+
|
|
|
+ if (USBi_UI & intr)
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "ui ");
|
|
|
+ intr &= ~USBi_UI;
|
|
|
+ if (USBi_UEI & intr)
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "uei ");
|
|
|
+ intr &= ~USBi_UEI;
|
|
|
+ if (USBi_PCI & intr)
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "pci ");
|
|
|
+ intr &= ~USBi_PCI;
|
|
|
+ if (USBi_URI & intr)
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "uri ");
|
|
|
+ intr &= ~USBi_URI;
|
|
|
+ if (USBi_SLI & intr)
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "sli ");
|
|
|
+ intr &= ~USBi_SLI;
|
|
|
+ if (intr)
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "??? ");
|
|
|
+ if (isr_statistics.hndl.buf[i])
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n, "\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_unlock_irqrestore(&udc->lock, flags);
|
|
|
+
|
|
|
+ return n;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * store_inters: enable & force or disable an individual interrutps
|
|
|
+ * (to be used for test purposes only)
|
|
|
+ *
|
|
|
+ * Check "device.h" for details
|
|
|
+ */
|
|
|
+static ssize_t store_inters(struct device *dev, struct device_attribute *attr,
|
|
|
+ const char *buf, size_t count)
|
|
|
+{
|
|
|
+ struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
|
|
|
+ unsigned long flags;
|
|
|
+ unsigned en, bit;
|
|
|
+
|
|
|
+ if (attr == NULL || buf == NULL) {
|
|
|
+ dev_err(udc->dev, "EINVAL\n");
|
|
|
+ goto done;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sscanf(buf, "%u %u", &en, &bit) != 2 || en > 1) {
|
|
|
+ dev_err(udc->dev, "<1|0> <bit>: enable|disable interrupt\n");
|
|
|
+ goto done;
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_lock_irqsave(&udc->lock, flags);
|
|
|
+ if (en) {
|
|
|
+ if (hw_intr_force(udc, bit))
|
|
|
+ dev_err(dev, "invalid bit number\n");
|
|
|
+ else
|
|
|
+ isr_statistics.test++;
|
|
|
+ } else {
|
|
|
+ if (hw_intr_clear(udc, bit))
|
|
|
+ dev_err(dev, "invalid bit number\n");
|
|
|
+ }
|
|
|
+ spin_unlock_irqrestore(&udc->lock, flags);
|
|
|
+
|
|
|
+ done:
|
|
|
+ return count;
|
|
|
+}
|
|
|
+static DEVICE_ATTR(inters, S_IRUSR | S_IWUSR, show_inters, store_inters);
|
|
|
+
|
|
|
+/**
|
|
|
+ * show_port_test: reads port test mode
|
|
|
+ *
|
|
|
+ * Check "device.h" for details
|
|
|
+ */
|
|
|
+static ssize_t show_port_test(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
|
|
|
+ unsigned long flags;
|
|
|
+ unsigned mode;
|
|
|
+
|
|
|
+ if (attr == NULL || buf == NULL) {
|
|
|
+ dev_err(udc->dev, "EINVAL\n");
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_lock_irqsave(&udc->lock, flags);
|
|
|
+ mode = hw_port_test_get(udc);
|
|
|
+ spin_unlock_irqrestore(&udc->lock, flags);
|
|
|
+
|
|
|
+ return scnprintf(buf, PAGE_SIZE, "mode = %u\n", mode);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * store_port_test: writes port test mode
|
|
|
+ *
|
|
|
+ * Check "device.h" for details
|
|
|
+ */
|
|
|
+static ssize_t store_port_test(struct device *dev,
|
|
|
+ struct device_attribute *attr,
|
|
|
+ const char *buf, size_t count)
|
|
|
+{
|
|
|
+ struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
|
|
|
+ unsigned long flags;
|
|
|
+ unsigned mode;
|
|
|
+
|
|
|
+ if (attr == NULL || buf == NULL) {
|
|
|
+ dev_err(udc->dev, "[%s] EINVAL\n", __func__);
|
|
|
+ goto done;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sscanf(buf, "%u", &mode) != 1) {
|
|
|
+ dev_err(udc->dev, "<mode>: set port test mode");
|
|
|
+ goto done;
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_lock_irqsave(&udc->lock, flags);
|
|
|
+ if (hw_port_test_set(udc, mode))
|
|
|
+ dev_err(udc->dev, "invalid mode\n");
|
|
|
+ spin_unlock_irqrestore(&udc->lock, flags);
|
|
|
+
|
|
|
+ done:
|
|
|
+ return count;
|
|
|
+}
|
|
|
+static DEVICE_ATTR(port_test, S_IRUSR | S_IWUSR,
|
|
|
+ show_port_test, store_port_test);
|
|
|
+
|
|
|
+/**
|
|
|
+ * show_qheads: DMA contents of all queue heads
|
|
|
+ *
|
|
|
+ * Check "device.h" for details
|
|
|
+ */
|
|
|
+static ssize_t show_qheads(struct device *dev, struct device_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
|
|
|
+ unsigned long flags;
|
|
|
+ unsigned i, j, n = 0;
|
|
|
+
|
|
|
+ if (attr == NULL || buf == NULL) {
|
|
|
+ dev_err(udc->dev, "[%s] EINVAL\n", __func__);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_lock_irqsave(&udc->lock, flags);
|
|
|
+ for (i = 0; i < udc->hw_ep_max/2; i++) {
|
|
|
+ struct ci13xxx_ep *mEpRx = &udc->ci13xxx_ep[i];
|
|
|
+ struct ci13xxx_ep *mEpTx =
|
|
|
+ &udc->ci13xxx_ep[i + udc->hw_ep_max/2];
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n,
|
|
|
+ "EP=%02i: RX=%08X TX=%08X\n",
|
|
|
+ i, (u32)mEpRx->qh.dma, (u32)mEpTx->qh.dma);
|
|
|
+ for (j = 0; j < (sizeof(struct ci13xxx_qh)/sizeof(u32)); j++) {
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n,
|
|
|
+ " %04X: %08X %08X\n", j,
|
|
|
+ *((u32 *)mEpRx->qh.ptr + j),
|
|
|
+ *((u32 *)mEpTx->qh.ptr + j));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ spin_unlock_irqrestore(&udc->lock, flags);
|
|
|
+
|
|
|
+ return n;
|
|
|
+}
|
|
|
+static DEVICE_ATTR(qheads, S_IRUSR, show_qheads, NULL);
|
|
|
+
|
|
|
+/**
|
|
|
+ * show_registers: dumps all registers
|
|
|
+ *
|
|
|
+ * Check "device.h" for details
|
|
|
+ */
|
|
|
+#define DUMP_ENTRIES 512
|
|
|
+static ssize_t show_registers(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
|
|
|
+ unsigned long flags;
|
|
|
+ u32 *dump;
|
|
|
+ unsigned i, k, n = 0;
|
|
|
+
|
|
|
+ if (attr == NULL || buf == NULL) {
|
|
|
+ dev_err(udc->dev, "[%s] EINVAL\n", __func__);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ dump = kmalloc(sizeof(u32) * DUMP_ENTRIES, GFP_KERNEL);
|
|
|
+ if (!dump) {
|
|
|
+ dev_err(udc->dev, "%s: out of memory\n", __func__);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_lock_irqsave(&udc->lock, flags);
|
|
|
+ k = hw_register_read(udc, dump, DUMP_ENTRIES);
|
|
|
+ spin_unlock_irqrestore(&udc->lock, flags);
|
|
|
+
|
|
|
+ for (i = 0; i < k; i++) {
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n,
|
|
|
+ "reg[0x%04X] = 0x%08X\n",
|
|
|
+ i * (unsigned)sizeof(u32), dump[i]);
|
|
|
+ }
|
|
|
+ kfree(dump);
|
|
|
+
|
|
|
+ return n;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * store_registers: writes value to register address
|
|
|
+ *
|
|
|
+ * Check "device.h" for details
|
|
|
+ */
|
|
|
+static ssize_t store_registers(struct device *dev,
|
|
|
+ struct device_attribute *attr,
|
|
|
+ const char *buf, size_t count)
|
|
|
+{
|
|
|
+ struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
|
|
|
+ unsigned long addr, data, flags;
|
|
|
+
|
|
|
+ if (attr == NULL || buf == NULL) {
|
|
|
+ dev_err(udc->dev, "[%s] EINVAL\n", __func__);
|
|
|
+ goto done;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sscanf(buf, "%li %li", &addr, &data) != 2) {
|
|
|
+ dev_err(udc->dev,
|
|
|
+ "<addr> <data>: write data to register address\n");
|
|
|
+ goto done;
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_lock_irqsave(&udc->lock, flags);
|
|
|
+ if (hw_register_write(udc, addr, data))
|
|
|
+ dev_err(udc->dev, "invalid address range\n");
|
|
|
+ spin_unlock_irqrestore(&udc->lock, flags);
|
|
|
+
|
|
|
+ done:
|
|
|
+ return count;
|
|
|
+}
|
|
|
+static DEVICE_ATTR(registers, S_IRUSR | S_IWUSR,
|
|
|
+ show_registers, store_registers);
|
|
|
+
|
|
|
+/**
|
|
|
+ * show_requests: DMA contents of all requests currently queued (all endpts)
|
|
|
+ *
|
|
|
+ * Check "device.h" for details
|
|
|
+ */
|
|
|
+static ssize_t show_requests(struct device *dev, struct device_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev);
|
|
|
+ unsigned long flags;
|
|
|
+ struct list_head *ptr = NULL;
|
|
|
+ struct ci13xxx_req *req = NULL;
|
|
|
+ unsigned i, j, n = 0, qSize = sizeof(struct ci13xxx_td)/sizeof(u32);
|
|
|
+
|
|
|
+ if (attr == NULL || buf == NULL) {
|
|
|
+ dev_err(udc->dev, "[%s] EINVAL\n", __func__);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_lock_irqsave(&udc->lock, flags);
|
|
|
+ for (i = 0; i < udc->hw_ep_max; i++)
|
|
|
+ list_for_each(ptr, &udc->ci13xxx_ep[i].qh.queue)
|
|
|
+ {
|
|
|
+ req = list_entry(ptr, struct ci13xxx_req, queue);
|
|
|
+
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n,
|
|
|
+ "EP=%02i: TD=%08X %s\n",
|
|
|
+ i % udc->hw_ep_max/2, (u32)req->dma,
|
|
|
+ ((i < udc->hw_ep_max/2) ? "RX" : "TX"));
|
|
|
+
|
|
|
+ for (j = 0; j < qSize; j++)
|
|
|
+ n += scnprintf(buf + n, PAGE_SIZE - n,
|
|
|
+ " %04X: %08X\n", j,
|
|
|
+ *((u32 *)req->ptr + j));
|
|
|
+ }
|
|
|
+ spin_unlock_irqrestore(&udc->lock, flags);
|
|
|
+
|
|
|
+ return n;
|
|
|
+}
|
|
|
+static DEVICE_ATTR(requests, S_IRUSR, show_requests, NULL);
|
|
|
+
|
|
|
+/**
|
|
|
+ * dbg_create_files: initializes the attribute interface
|
|
|
+ * @dev: device
|
|
|
+ *
|
|
|
+ * This function returns an error code
|
|
|
+ */
|
|
|
+int dbg_create_files(struct device *dev)
|
|
|
+{
|
|
|
+ int retval = 0;
|
|
|
+
|
|
|
+ if (dev == NULL)
|
|
|
+ return -EINVAL;
|
|
|
+ retval = device_create_file(dev, &dev_attr_device);
|
|
|
+ if (retval)
|
|
|
+ goto done;
|
|
|
+ retval = device_create_file(dev, &dev_attr_driver);
|
|
|
+ if (retval)
|
|
|
+ goto rm_device;
|
|
|
+ retval = device_create_file(dev, &dev_attr_events);
|
|
|
+ if (retval)
|
|
|
+ goto rm_driver;
|
|
|
+ retval = device_create_file(dev, &dev_attr_inters);
|
|
|
+ if (retval)
|
|
|
+ goto rm_events;
|
|
|
+ retval = device_create_file(dev, &dev_attr_port_test);
|
|
|
+ if (retval)
|
|
|
+ goto rm_inters;
|
|
|
+ retval = device_create_file(dev, &dev_attr_qheads);
|
|
|
+ if (retval)
|
|
|
+ goto rm_port_test;
|
|
|
+ retval = device_create_file(dev, &dev_attr_registers);
|
|
|
+ if (retval)
|
|
|
+ goto rm_qheads;
|
|
|
+ retval = device_create_file(dev, &dev_attr_requests);
|
|
|
+ if (retval)
|
|
|
+ goto rm_registers;
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ rm_registers:
|
|
|
+ device_remove_file(dev, &dev_attr_registers);
|
|
|
+ rm_qheads:
|
|
|
+ device_remove_file(dev, &dev_attr_qheads);
|
|
|
+ rm_port_test:
|
|
|
+ device_remove_file(dev, &dev_attr_port_test);
|
|
|
+ rm_inters:
|
|
|
+ device_remove_file(dev, &dev_attr_inters);
|
|
|
+ rm_events:
|
|
|
+ device_remove_file(dev, &dev_attr_events);
|
|
|
+ rm_driver:
|
|
|
+ device_remove_file(dev, &dev_attr_driver);
|
|
|
+ rm_device:
|
|
|
+ device_remove_file(dev, &dev_attr_device);
|
|
|
+ done:
|
|
|
+ return retval;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dbg_remove_files: destroys the attribute interface
|
|
|
+ * @dev: device
|
|
|
+ *
|
|
|
+ * This function returns an error code
|
|
|
+ */
|
|
|
+int dbg_remove_files(struct device *dev)
|
|
|
+{
|
|
|
+ if (dev == NULL)
|
|
|
+ return -EINVAL;
|
|
|
+ device_remove_file(dev, &dev_attr_requests);
|
|
|
+ device_remove_file(dev, &dev_attr_registers);
|
|
|
+ device_remove_file(dev, &dev_attr_qheads);
|
|
|
+ device_remove_file(dev, &dev_attr_port_test);
|
|
|
+ device_remove_file(dev, &dev_attr_inters);
|
|
|
+ device_remove_file(dev, &dev_attr_events);
|
|
|
+ device_remove_file(dev, &dev_attr_driver);
|
|
|
+ device_remove_file(dev, &dev_attr_device);
|
|
|
+ return 0;
|
|
|
+}
|