@@ -0,0 +1,189 @@
+Introduction
+============
+The Char SDIO Device Driver is an interface which exposes an SDIO
+card/function from kernel space as a char device in user space.
+
+The driver doesn't interact with any HW directly. It relies on SDIO
+card/function interface provided as a part of Linux kernel.
+
+Hardware description
+====================
+Each SDIO device/card contains an SDIO client HW block.
+The host interacts with the device by sending byte sequences called
+command (CMD). Some commands can be followed by data blocks. The
+device sends back a byte sequence called response (R) and a data
+block if required. CMD3, CMD5 and CMD7 are used to initialize the
+device. CMD52 and CMD53 are used to access the device. Command
+format and properties are defined by SDIO Specification document
+published by SD Association:
+ http://www.sdcard.org/developers/tech/sdio/.
+
+CMD52 and CMD53 can access up to 8 address spaces called Functions.
+Function 0 contains system information predefined by SD/SDIO
+standard and Functions 1-7 are defined by the SDIO device
+manufacturer.
+
+An SDIO device/card can send an interrupt to SDIO host. This
+interrupt is intercepted and handled by SDIO host.
+
+Software description
+====================
+Linux provides a framework for handling SDIO devices. It implements
+kind of plug-and-play model in which the Linux SDIO Host Driver is
+responsible for initializing an SDIO device upon insertion. It also
+reads device/card identification information and enumerates functions
+provided by the device and then looks up in the list of
+preregistered user SDIO drivers for a suitable one.
+
+During its lifecycle the user SDIO driver interacts with the Linux
+SDIO Host Driver in order to send/receive information to/from SDIO
+device/card. The user SDIO driver doesn't work with CMD52/CMD53
+directly. Instead it uses an abstraction provided by the Linux SDIO
+Host Driver.
+
+The Linux SDIO Host Driver is also in charge of handling SDIO
+interrupts. User SDIO driver can register its own callback in SDIO
+Host Driver and get a notification about interrupt event.
+
+The Char SDIO Device Driver follows the design guidelines mentioned
+above. It provides the following functionality:
+
+ - Register itself in the user SDIO drivers list;
+ - Handle Probe event upon insertion of supported card/device;
+ - Creates and maintains a char device driver for each SDIO Function
+ found in the card/device;
+ - Translates read/write/ioctl calls to appropriate SDIO call
+ sequences;
+
+In order to handle general SDIO configuration functionality and
+Function 0 the Char SDIO Device Driver provides additional
+simplified char device driver.
+
+The Manufacturer and Device IDs of handled SDIO device should be
+provided as parameters for kernel module or as configuration
+parameters in case of statically linked driver.
+
+Design
+======
+The main goal of the Char SDIO Device Driver is to expose an SDIO
+card/device from kernel space to user space as a char device driver.
+The driver should be generic and simple as far as possible.
+
+The biggest design tradeoff is maintaining a balance between the
+system call overhead required to initiate an SDIO transaction from
+user space and overall SDIO communication performance. But luckily,
+because of nature of SDIO protocol, this overhead is negligible
+comparing to time required to execute SDIO transaction itself. So,
+each CMD52 (read or write) consists from single ioctl system call.
+And each CMD53 invokes single ioctl system call followed by read or
+write system call.
+
+The Char SDIO Device Driver registers its own class of the devices
+called 'csdio'. This class will serve as a common roof for all SDIO
+devices served by different instances of the Char SDIO Device Driver.
+Additional benefit from maintaining its own class is the driver
+ability to overwrite default permissions of the dev nodes created by
+the driver.
+
+Power Management
+================
+None
+
+SMP/multi-core
+==============
+The driver does not anticipate any issues related to multi-core
+since it is expected to run on one core only.
+
+Security
+========
+None
+
+Performance
+===========
+None
+
+Interface
+=========
+The Char SDIO Device Driver has two char device interfaces:
+ - Control Interface;
+ - Function Interface.
+
+Char SDIO Device Driver Control Interface consists of:
+ - open() - device node is /dev/csdio0;
+ - close()
+ - ioctl() - the following options are available:
+ - CSDIO_IOC_ENABLE_HIGHSPEED_MODE;
+ - CSDIO_IOC_SET_DATA_TRANSFER_CLOCKS;
+ - CSDIO_IOC_ENABLE_ISR;
+ - CSDIO_IOC_DISABLE_ISR.
+
+Char SDIO Device Driver Function Interface consists of:
+ - open() - device node is /dev/csdiofX, where X is Function Id;
+ - close()
+ - read() - send CMD53 read;
+ - write() - send CMD53 write;
+ - ioctl() - the following options are available:
+ - CSDIO_IOC_SET_OP_CODE - 0 fixed adrress, 1 autoincrement.
+ - CSDIO_IOC_FUNCTION_SET_BLOCK_SIZE;
+ - CSDIO_IOC_SET_BLOCK_MODE - 0 byte mode, 1 block mode;
+ - CSDIO_IOC_CMD52 - execute CMD52, receives the
+ following structure as a parameter:
+ struct csdio_cmd52_ctrl_t {
+ uint32_t m_write; // 0 - read, 1 -write
+ uint32_t m_address;
+ uint32_t m_data; // data to write or read data
+ uint32_t m_ret; // command execution status
+ }__attribute__ ((packed));
+ - CSDIO_IOC_CMD53 - setup CMD53 data transfer, receives the
+ following structure as a parameter:
+ struct csdio_cmd53_ctrl_t {
+ uint32_t m_block_mode;
+ uint32_t m_op_code;
+ uint32_t m_address;
+ }__attribute__ ((packed));
+ - CSDIO_IOC_CONNECT_ISR;
+ - CSDIO_IOC_DISCONNECT_ISR;
+ - CSDIO_IOC_GET_VDD;
+ - CSDIO_IOC_SET_VDD.
+
+Additionally, user space application can use fcntl system call with
+parameters F_SETOWN and F_SETFL in order to set an asynchronous
+callback for SDIO interrupt.
+
+Driver parameters
+=================
+If the driver is compiled as a kernel module, the following
+parameters can be used in order to provide Manufacturer and Device IDs
+upon module download:
+ - csdio_vendor_id;
+ - csdio_device_id.
+If the driver is intended to work with specific SDIO host the
+host_name parameter should be added followed by the name of the MMC
+host platform device.
+
+Config options
+==============
+These are the kernel configuration options:
+ - CONFIG_CSDIO_VENDOR_ID;
+ - CONFIG_CSDIO_DEVICE_ID.
+
+Dependencies
+============
+The Char SDIO Device Driver depends on Linux SDIO Host Driver.
+
+User space utilities
+====================
+None
+
+Other
+=====
+None
+
+Known issues
+============
+None
+
+To do
+=====
+Provide mechanism to support a number of SDIO devices simultaneously
+connected to different SDIO hosts.
@@ -600,5 +600,28 @@ config TILE_SROM
device appear much like a simple EEPROM, and knows
how to partition a single ROM for multiple purposes.
+config MMC_GENERIC_CSDIO
+ tristate "Generic sdio driver"
+ default n
+ help
+ SDIO function driver that extends SDIO card as character device
+ in user space.
+
+config CSDIO_VENDOR_ID
+ hex "Card VendorId"
+ depends on MMC_GENERIC_CSDIO
+ default "0"
+ help
+ Enter vendor id for targeted sdio device, this may be overwritten by
+ module parameters.
+
+config CSDIO_DEVICE_ID
+ hex "CardDeviceId"
+ depends on MMC_GENERIC_CSDIO
+ default "0"
+ help
+ Enter device id for targeted sdio device, this may be overwritten by
+ module parameters.
+.
endmenu
@@ -61,3 +61,4 @@ obj-$(CONFIG_JS_RTC) += js-rtc.o
js-rtc-y = rtc.o
obj-$(CONFIG_TILE_SROM) += tile-srom.o
+obj-$(CONFIG_MMC_GENERIC_CSDIO) += csdio.o
@@ -0,0 +1,1070 @@
+/*
+ * Copyright (c) 2010, Code Aurora Forum. 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 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/serial_reg.h>
+#include <linux/circ_buf.h>
+#include <linux/gfp.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+/* Char device */
+#include <linux/cdev.h>
+#include <linux/fs.h>
+
+/* Sdio device */
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+
+#include <linux/csdio.h>
+
+#define VERSION "0.5"
+#define CSDIO_NUM_OF_SDIO_FUNCTIONS 7
+#define CSDIO_DEV_NAME "csdio"
+#define TP_DEV_NAME CSDIO_DEV_NAME"f"
+#define CSDIO_DEV_PERMISSIONS 0666
+
+#define CSDIO_SDIO_BUFFER_SIZE (64*512)
+
+int csdio_major;
+int csdio_minor;
+int csdio_transport_nr_devs = CSDIO_NUM_OF_SDIO_FUNCTIONS;
+static uint csdio_vendor_id;
+static uint csdio_device_id;
+static char *host_name;
+
+static struct csdio_func_t {
+ struct sdio_func *m_func;
+ int m_enabled;
+ struct cdev m_cdev; /* char device structure */
+ struct device *m_device;
+ u32 m_block_size;
+} *g_csdio_func_table[CSDIO_NUM_OF_SDIO_FUNCTIONS] = {0};
+
+struct csdio_t {
+ struct cdev m_cdev;
+ struct device *m_device;
+ struct class *m_driver_class;
+ struct fasync_struct *m_async_queue;
+ unsigned char m_current_irq_mask; /* currently enabled irqs */
+ struct mmc_host *m_host;
+ unsigned int m_num_of_func;
+} g_csdio;
+
+struct csdio_file_descriptor {
+ struct csdio_func_t *m_port;
+ u32 m_block_mode;/* data tran. byte(0)/block(1) */
+ u32 m_op_code; /* address auto increment flag */
+ u32 m_address;
+};
+
+static void *g_sdio_buffer;
+
+/*
+ * Open and release
+ */
+static int csdio_transport_open(struct inode *inode, struct file *filp)
+{
+ int ret = 0;
+ struct csdio_func_t *port = NULL; /* device information */
+ struct sdio_func *func = NULL;
+ struct csdio_file_descriptor *descriptor = NULL;
+
+ port = container_of(inode->i_cdev, struct csdio_func_t, m_cdev);
+ func = port->m_func;
+ descriptor = kzalloc(sizeof(struct csdio_file_descriptor), GFP_KERNEL);
+ if (!descriptor) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ pr_info(TP_DEV_NAME"%d: open: func=%p, port=%p\n",
+ func->num, func, port);
+ sdio_claim_host(func);
+ ret = sdio_enable_func(func);
+ if (ret) {
+ pr_err(TP_DEV_NAME"%d:Enable func failed (%d)\n",
+ func->num, ret);
+ ret = -EIO;
+ goto free_descriptor;
+ }
+ descriptor->m_port = port;
+ filp->private_data = descriptor;
+ goto release_host;
+
+free_descriptor:
+ kfree(descriptor);
+release_host:
+ sdio_release_host(func);
+exit:
+ return ret;
+}
+
+static int csdio_transport_release(struct inode *inode, struct file *filp)
+{
+ int ret = 0;
+ struct csdio_file_descriptor *descriptor = filp->private_data;
+ struct csdio_func_t *port = descriptor->m_port;
+ struct sdio_func *func = port->m_func;
+
+ pr_info(TP_DEV_NAME"%d: release\n", func->num);
+ sdio_claim_host(func);
+ ret = sdio_disable_func(func);
+ if (ret) {
+ pr_err(TP_DEV_NAME"%d:Disable func failed(%d)\n",
+ func->num, ret);
+ ret = -EIO;
+ }
+ sdio_release_host(func);
+ kfree(descriptor);
+ return ret;
+}
+
+/*
+ * Data management: read and write
+ */
+static ssize_t csdio_transport_read(struct file *filp,
+ char __user *buf,
+ size_t count,
+ loff_t *f_pos)
+{
+ ssize_t ret = 0;
+ struct csdio_file_descriptor *descriptor = filp->private_data;
+ struct csdio_func_t *port = descriptor->m_port;
+ struct sdio_func *func = port->m_func;
+ size_t t_count = count;
+
+ if (descriptor->m_block_mode) {
+ pr_info(TP_DEV_NAME "%d: CMD53 read, Md:%d, Addr:0x%04X,"
+ " Un:%zd (Bl:%zd, BlSz:%d)\n", func->num,
+ descriptor->m_block_mode,
+ descriptor->m_address,
+ count*port->m_block_size,
+ count, port->m_block_size);
+ /* recalculate size */
+ count *= port->m_block_size;
+ }
+ sdio_claim_host(func);
+ if (descriptor->m_op_code) {
+ /* auto increment */
+ ret = sdio_memcpy_fromio(func, g_sdio_buffer,
+ descriptor->m_address, count);
+ } else { /* FIFO */
+ ret = sdio_readsb(func, g_sdio_buffer,
+ descriptor->m_address, count);
+ }
+ sdio_release_host(func);
+ if (!ret) {
+ if (copy_to_user(buf, g_sdio_buffer, count))
+ ret = -EFAULT;
+ else
+ ret = t_count;
+ }
+ if (ret < 0) {
+ pr_err(TP_DEV_NAME "%d: CMD53 read failed (%zd)"
+ "(Md:%d, Addr:0x%04X, Sz:%zd)\n",
+ func->num, ret,
+ descriptor->m_block_mode,
+ descriptor->m_address, count);
+ }
+ return ret;
+}
+
+static ssize_t csdio_transport_write(struct file *filp,
+ const char __user *buf,
+ size_t count,
+ loff_t *f_pos)
+{
+ ssize_t ret = 0;
+ struct csdio_file_descriptor *descriptor = filp->private_data;
+ struct csdio_func_t *port = descriptor->m_port;
+ struct sdio_func *func = port->m_func;
+ size_t t_count = count;
+
+ if (descriptor->m_block_mode)
+ count *= port->m_block_size;
+
+ if (copy_from_user(g_sdio_buffer, buf, count)) {
+ pr_err(TP_DEV_NAME"%d:copy_from_user failed\n", func->num);
+ ret = -EFAULT;
+ } else {
+ sdio_claim_host(func);
+ if (descriptor->m_op_code) {
+ /* auto increment */
+ ret = sdio_memcpy_toio(func, descriptor->m_address,
+ g_sdio_buffer, count);
+ } else {
+ /* FIFO */
+ ret = sdio_writesb(func, descriptor->m_address,
+ g_sdio_buffer, count);
+ }
+ sdio_release_host(func);
+ if (!ret) {
+ ret = t_count;
+ } else {
+ pr_err(TP_DEV_NAME "%d: CMD53 write failed (%zd)"
+ "(Md:%d, Addr:0x%04X, Sz:%zd)\n",
+ func->num, ret, descriptor->m_block_mode,
+ descriptor->m_address, count);
+ }
+ }
+ return ret;
+}
+
+/* disable interrupt for sdio client */
+static int disable_sdio_client_isr(struct sdio_func *func)
+{
+ int ret;
+
+ /* disable for all functions, to restore interrupts
+ * use g_csdio.m_current_irq_mask */
+ sdio_f0_writeb(func, 0, SDIO_CCCR_IENx, &ret);
+ if (ret)
+ pr_err(CSDIO_DEV_NAME" Can't sdio_f0_writeb (%d)\n", ret);
+
+ return ret;
+}
+
+/*
+ * This handles the interrupt from SDIO.
+ */
+static void csdio_sdio_irq(struct sdio_func *func)
+{
+ int ret;
+
+ pr_info(CSDIO_DEV_NAME" csdio_sdio_irq: func=%d\n", func->num);
+ ret = disable_sdio_client_isr(func);
+ if (ret) {
+ pr_err(CSDIO_DEV_NAME" Can't disable client isr(%d)\n", ret);
+ return;
+ }
+ /* signal asynchronous readers */
+ if (g_csdio.m_async_queue)
+ kill_fasync(&g_csdio.m_async_queue, SIGIO, POLL_IN);
+}
+
+/*
+ * The ioctl() implementation
+ */
+static long csdio_transport_ioctl(struct file *filp,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ int err = 0;
+ int ret = 0;
+ struct csdio_file_descriptor *descriptor = filp->private_data;
+ struct csdio_func_t *port = descriptor->m_port;
+ struct sdio_func *func = port->m_func;
+
+ /* extract the type and number bitfields
+ sanity check: return ENOTTY (inappropriate ioctl) before
+ access_ok()
+ */
+ if ((_IOC_TYPE(cmd) != CSDIO_IOC_MAGIC) ||
+ (_IOC_NR(cmd) > CSDIO_IOC_MAXNR)) {
+ pr_err(TP_DEV_NAME "Wrong ioctl command parameters\n");
+ ret = -ENOTTY;
+ goto exit;
+ }
+
+ /* the direction is a bitmask, and VERIFY_WRITE catches R/W
+ * transfers. `Type' is user-oriented, while access_ok is
+ kernel-oriented, so the concept of "read" and "write" is reversed
+ */
+ if (_IOC_DIR(cmd) & _IOC_READ) {
+ err = !access_ok(VERIFY_WRITE, (void __user *)arg,
+ _IOC_SIZE(cmd));
+ } else {
+ if (_IOC_DIR(cmd) & _IOC_WRITE) {
+ err = !access_ok(VERIFY_READ, (void __user *)arg,
+ _IOC_SIZE(cmd));
+ }
+ }
+ if (err) {
+ pr_err(TP_DEV_NAME "Wrong ioctl access direction\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ switch (cmd) {
+ case CSDIO_IOC_SET_OP_CODE:
+ {
+ pr_info(TP_DEV_NAME"%d:SET_OP_CODE=%d\n",
+ func->num, descriptor->m_op_code);
+ ret = get_user(descriptor->m_op_code,
+ (unsigned char __user *)arg);
+ if (ret) {
+ pr_err(TP_DEV_NAME"%d:SET_OP_CODE get data"
+ " from user space failed(%d)\n",
+ func->num, ret);
+ ret = -ENOTTY;
+ break;
+ }
+ }
+ break;
+ case CSDIO_IOC_FUNCTION_SET_BLOCK_SIZE:
+ {
+ unsigned block_size;
+
+ ret = get_user(block_size, (unsigned __user *)arg);
+ if (ret) {
+ pr_err(TP_DEV_NAME"%d:SET_BLOCK_SIZE get data"
+ " from user space failed(%d)\n",
+ func->num, ret);
+ ret = -ENOTTY;
+ break;
+ }
+ pr_info(TP_DEV_NAME"%d:SET_BLOCK_SIZE=%d\n",
+ func->num, block_size);
+ sdio_claim_host(func);
+ ret = sdio_set_block_size(func, block_size);
+ if (!ret) {
+ port->m_block_size = block_size;
+ } else {
+ pr_err(TP_DEV_NAME"%d:SET_BLOCK_SIZE set block"
+ " size to %d failed (%d)\n",
+ func->num, block_size, ret);
+ ret = -ENOTTY;
+ break;
+ }
+ sdio_release_host(func);
+ }
+ break;
+ case CSDIO_IOC_SET_BLOCK_MODE:
+ {
+ pr_info(TP_DEV_NAME"%d:SET_BLOCK_MODE=%d\n",
+ func->num, descriptor->m_block_mode);
+ ret = get_user(descriptor->m_block_mode,
+ (unsigned char __user *)arg);
+ if (ret) {
+ pr_err(TP_DEV_NAME"%d:SET_BLOCK_MODE get data"
+ " from user space failed\n",
+ func->num);
+ ret = -ENOTTY;
+ break;
+ }
+ }
+ break;
+ case CSDIO_IOC_CMD52:
+ {
+ struct csdio_cmd52_ctrl_t cmd52ctrl;
+ int cmd52ret;
+
+ if (copy_from_user(&cmd52ctrl,
+ (const unsigned char __user *)arg,
+ sizeof(cmd52ctrl))) {
+ pr_err(TP_DEV_NAME"%d:IOC_CMD52 get data"
+ " from user space failed\n",
+ func->num);
+ ret = -ENOTTY;
+ break;
+ }
+ sdio_claim_host(func);
+ if (cmd52ctrl.m_write)
+ sdio_writeb(func, cmd52ctrl.m_data,
+ cmd52ctrl.m_address, &cmd52ret);
+ else
+ cmd52ctrl.m_data = sdio_readb(func,
+ cmd52ctrl.m_address, &cmd52ret);
+
+ cmd52ctrl.m_ret = cmd52ret;
+ sdio_release_host(func);
+ if (cmd52ctrl.m_ret)
+ pr_err(TP_DEV_NAME"%d:IOC_CMD52 failed (%d)\n",
+ func->num, cmd52ctrl.m_ret);
+
+ if (copy_to_user((unsigned char __user *)arg,
+ &cmd52ctrl,
+ sizeof(cmd52ctrl))) {
+ pr_err(TP_DEV_NAME"%d:IOC_CMD52 put data"
+ " to user space failed\n",
+ func->num);
+ ret = -ENOTTY;
+ break;
+ }
+ }
+ break;
+ case CSDIO_IOC_CMD53:
+ {
+ struct csdio_cmd53_ctrl_t csdio_cmd53_ctrl;
+
+ if (copy_from_user(&csdio_cmd53_ctrl,
+ (const char __user *)arg,
+ sizeof(csdio_cmd53_ctrl))) {
+ ret = -EPERM;
+ pr_err(TP_DEV_NAME"%d:"
+ "Get data from user space failed\n",
+ func->num);
+ break;
+ }
+ descriptor->m_block_mode =
+ csdio_cmd53_ctrl.m_block_mode;
+ descriptor->m_op_code = csdio_cmd53_ctrl.m_op_code;
+ descriptor->m_address = csdio_cmd53_ctrl.m_address;
+ }
+ break;
+ case CSDIO_IOC_CONNECT_ISR:
+ {
+ pr_info(CSDIO_DEV_NAME" SDIO_CONNECT_ISR"
+ " func=%d, csdio_sdio_irq=%pK\n",
+ func->num, csdio_sdio_irq);
+ sdio_claim_host(func);
+ ret = sdio_claim_irq(func, csdio_sdio_irq);
+ sdio_release_host(func);
+ if (ret) {
+ pr_err(CSDIO_DEV_NAME" SDIO_CONNECT_ISR"
+ " claim irq failed(%d)\n", ret);
+ } else {
+ /* update current irq mask for disable/enable */
+ g_csdio.m_current_irq_mask |= (1 << func->num);
+ }
+ }
+ break;
+ case CSDIO_IOC_DISCONNECT_ISR:
+ {
+ pr_info(CSDIO_DEV_NAME " SDIO_DISCONNECT_ISR func=%d\n",
+ func->num);
+ sdio_claim_host(func);
+ sdio_release_irq(func);
+ sdio_release_host(func);
+ /* update current irq mask for disable/enable */
+ g_csdio.m_current_irq_mask &= ~(1 << func->num);
+ }
+ break;
+ default: /* redundant, as cmd was checked against MAXNR */
+ pr_warning(TP_DEV_NAME"%d: Redundant IOCTL\n",
+ func->num);
+ ret = -ENOTTY;
+ }
+exit:
+ return ret;
+}
+
+static const struct file_operations csdio_transport_fops = {
+ .owner = THIS_MODULE,
+ .read = csdio_transport_read,
+ .write = csdio_transport_write,
+ .unlocked_ioctl = csdio_transport_ioctl,
+ .open = csdio_transport_open,
+ .release = csdio_transport_release,
+};
+
+static void csdio_transport_cleanup(struct csdio_func_t *port)
+{
+ int devno = MKDEV(csdio_major, csdio_minor + port->m_func->num);
+ device_destroy(g_csdio.m_driver_class, devno);
+ port->m_device = NULL;
+ cdev_del(&port->m_cdev);
+}
+
+#if defined(CONFIG_DEVTMPFS)
+static inline int csdio_cdev_update_permissions(
+ const char *devname, int dev_minor)
+{
+ return 0;
+}
+#else
+static int csdio_cdev_update_permissions(
+ const char *devname, int dev_minor)
+{
+ int ret = 0;
+ mm_segment_t fs;
+ struct file *file;
+ struct inode *inode;
+ struct iattr newattrs;
+ int mode = CSDIO_DEV_PERMISSIONS;
+ char dev_file[64];
+
+ fs = get_fs();
+ set_fs(get_ds());
+
+ snprintf(dev_file, sizeof(dev_file), "/dev/%s%d",
+ devname, dev_minor);
+ file = filp_open(dev_file, O_RDWR, 0);
+ if (IS_ERR(file)) {
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ inode = file->f_path.dentry->d_inode;
+
+ mutex_lock(&inode->i_mutex);
+ newattrs.ia_mode =
+ (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
+ newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
+ ret = notify_change(file->f_path.dentry, &newattrs);
+ mutex_unlock(&inode->i_mutex);
+
+ filp_close(file, NULL);
+
+exit:
+ set_fs(fs);
+ return ret;
+}
+#endif
+
+static struct device *csdio_cdev_init(struct cdev *char_dev,
+ const struct file_operations *file_op, int dev_minor,
+ const char *devname, struct device *parent)
+{
+ int ret = 0;
+ struct device *new_device = NULL;
+ dev_t devno = MKDEV(csdio_major, dev_minor);
+
+ /* Initialize transport device */
+ cdev_init(char_dev, file_op);
+ char_dev->owner = THIS_MODULE;
+ char_dev->ops = file_op;
+ ret = cdev_add(char_dev, devno, 1);
+
+ /* Fail gracefully if need be */
+ if (ret) {
+ pr_warning("Error %d adding CSDIO char device '%s%d'",
+ ret, devname, dev_minor);
+ goto exit;
+ }
+ pr_info("'%s%d' char driver registered\n", devname, dev_minor);
+
+ /* create a /dev entry for transport drivers */
+ new_device = device_create(g_csdio.m_driver_class, parent, devno, NULL,
+ "%s%d", devname, dev_minor);
+ if (!new_device) {
+ pr_err("Can't create device node '/dev/%s%d'\n",
+ devname, dev_minor);
+ goto cleanup;
+ }
+ /* no irq attached */
+ g_csdio.m_current_irq_mask = 0;
+
+ if (csdio_cdev_update_permissions(devname, dev_minor)) {
+ pr_warning("%s%d: Unable to update access permissions of the"
+ " '/dev/%s%d'\n",
+ devname, dev_minor, devname, dev_minor);
+ }
+
+ pr_info("%s%d: Device node '/dev/%s%d' created successfully\n",
+ devname, dev_minor, devname, dev_minor);
+ goto exit;
+cleanup:
+ cdev_del(char_dev);
+exit:
+ return new_device;
+}
+
+/* Looks for first non empty function, returns NULL otherwise */
+static struct sdio_func *get_active_func(void)
+{
+ int i;
+
+ for (i = 0; i < CSDIO_NUM_OF_SDIO_FUNCTIONS; i++) {
+ if (g_csdio_func_table[i])
+ return g_csdio_func_table[i]->m_func;
+ }
+ return NULL;
+}
+
+static ssize_t
+show_vdd(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ if (NULL == g_csdio.m_host)
+ return snprintf(buf, PAGE_SIZE, "N/A\n");
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ g_csdio.m_host->ios.vdd);
+}
+
+static int
+set_vdd_helper(int value)
+{
+ struct mmc_ios *ios = NULL;
+
+ if (NULL == g_csdio.m_host) {
+ pr_err("%s0: Set VDD, no MMC host assigned\n", CSDIO_DEV_NAME);
+ return -ENXIO;
+ }
+
+ mmc_claim_host(g_csdio.m_host);
+ ios = &g_csdio.m_host->ios;
+ ios->vdd = value;
+ g_csdio.m_host->ops->set_ios(g_csdio.m_host, ios);
+ mmc_release_host(g_csdio.m_host);
+ return 0;
+}
+
+static ssize_t
+set_vdd(struct device *dev, struct device_attribute *att,
+ const char *buf, size_t count)
+{
+ int value = 0;
+
+ sscanf(buf, "%d", &value);
+ if (set_vdd_helper(value))
+ return -ENXIO;
+ return count;
+}
+
+static DEVICE_ATTR(vdd, S_IRUGO | S_IWUSR,
+ show_vdd, set_vdd);
+
+static struct attribute *dev_attrs[] = {
+ &dev_attr_vdd.attr,
+ NULL,
+};
+
+static struct attribute_group dev_attr_grp = {
+ .attrs = dev_attrs,
+};
+
+/*
+ * The ioctl() implementation for control device
+ */
+static long csdio_ctrl_ioctl(struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+ int ret = 0;
+
+ pr_info("CSDIO ctrl ioctl.\n");
+
+ /* extract the type and number bitfields
+ sanity check: return ENOTTY (inappropriate ioctl) before
+ access_ok()
+ */
+ if ((_IOC_TYPE(cmd) != CSDIO_IOC_MAGIC) ||
+ (_IOC_NR(cmd) > CSDIO_IOC_MAXNR)) {
+ pr_err(CSDIO_DEV_NAME "Wrong ioctl command parameters\n");
+ ret = -ENOTTY;
+ goto exit;
+ }
+
+ /* the direction is a bitmask, and VERIFY_WRITE catches R/W
+ transfers. `Type' is user-oriented, while access_ok is
+ kernel-oriented, so the concept of "read" and "write" is reversed
+ */
+ if (_IOC_DIR(cmd) & _IOC_READ) {
+ err = !access_ok(VERIFY_WRITE, (void __user *)arg,
+ _IOC_SIZE(cmd));
+ } else {
+ if (_IOC_DIR(cmd) & _IOC_WRITE)
+ err = !access_ok(VERIFY_READ, (void __user *)arg,
+ _IOC_SIZE(cmd));
+ }
+ if (err) {
+ pr_err(CSDIO_DEV_NAME "Wrong ioctl access direction\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ switch (cmd) {
+ case CSDIO_IOC_ENABLE_HIGHSPEED_MODE:
+ pr_info(CSDIO_DEV_NAME" ENABLE_HIGHSPEED_MODE\n");
+ break;
+ case CSDIO_IOC_SET_DATA_TRANSFER_CLOCKS:
+ {
+ struct mmc_host *host = g_csdio.m_host;
+ struct mmc_ios *ios = NULL;
+
+ if (NULL == host) {
+ pr_err("%s0: "
+ "CSDIO_IOC_SET_DATA_TRANSFER_CLOCKS,"
+ " no MMC host assigned\n",
+ CSDIO_DEV_NAME);
+ ret = -EFAULT;
+ goto exit;
+ }
+ ios = &host->ios;
+
+ mmc_claim_host(host);
+ ret = get_user(host->ios.clock,
+ (unsigned int __user *)arg);
+ if (ret) {
+ pr_err(CSDIO_DEV_NAME
+ " get data from user space failed\n");
+ } else {
+ pr_err(CSDIO_DEV_NAME
+ "SET_DATA_TRANSFER_CLOCKS(%d-%d)(%d)\n",
+ host->f_min, host->f_max,
+ host->ios.clock);
+ host->ops->set_ios(host, ios);
+ }
+ mmc_release_host(host);
+ }
+ break;
+ case CSDIO_IOC_ENABLE_ISR:
+ {
+ int ret;
+ unsigned char reg;
+ struct sdio_func *func = get_active_func();
+
+ if (!func) {
+ pr_err(CSDIO_DEV_NAME " CSDIO_IOC_ENABLE_ISR"
+ " no active sdio function\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+ pr_info(CSDIO_DEV_NAME
+ " CSDIO_IOC_ENABLE_ISR func=%d\n",
+ func->num);
+ reg = g_csdio.m_current_irq_mask | 1;
+
+ sdio_claim_host(func);
+ sdio_f0_writeb(func, reg, SDIO_CCCR_IENx, &ret);
+ sdio_release_host(func);
+ if (ret) {
+ pr_err(CSDIO_DEV_NAME
+ " Can't sdio_f0_writeb (%d)\n",
+ ret);
+ goto exit;
+ }
+ }
+ break;
+ case CSDIO_IOC_DISABLE_ISR:
+ {
+ int ret;
+ struct sdio_func *func = get_active_func();
+ if (!func) {
+ pr_err(CSDIO_DEV_NAME " CSDIO_IOC_ENABLE_ISR"
+ " no active sdio function\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+ pr_info(CSDIO_DEV_NAME
+ " CSDIO_IOC_DISABLE_ISR func=%p\n",
+ func);
+
+ sdio_claim_host(func);
+ ret = disable_sdio_client_isr(func);
+ sdio_release_host(func);
+ if (ret) {
+ pr_err("%s0: Can't disable client isr (%d)\n",
+ CSDIO_DEV_NAME, ret);
+ goto exit;
+ }
+ }
+ break;
+ case CSDIO_IOC_SET_VDD:
+ {
+ unsigned int vdd = 0;
+
+ ret = get_user(vdd, (unsigned int __user *)arg);
+ if (ret) {
+ pr_err("%s0: CSDIO_IOC_SET_VDD,"
+ " get data from user space failed\n",
+ CSDIO_DEV_NAME);
+ goto exit;
+ }
+ pr_info(CSDIO_DEV_NAME" CSDIO_IOC_SET_VDD - %d\n", vdd);
+
+ ret = set_vdd_helper(vdd);
+ if (ret)
+ goto exit;
+ }
+ break;
+ case CSDIO_IOC_GET_VDD:
+ {
+ if (NULL == g_csdio.m_host) {
+ pr_err("%s0: CSDIO_IOC_GET_VDD,"
+ " no MMC host assigned\n",
+ CSDIO_DEV_NAME);
+ ret = -EFAULT;
+ goto exit;
+ }
+ ret = put_user(g_csdio.m_host->ios.vdd,
+ (unsigned short __user *)arg);
+ if (ret) {
+ pr_err("%s0: CSDIO_IOC_GET_VDD, put data"
+ " to user space failed\n",
+ CSDIO_DEV_NAME);
+ goto exit;
+ }
+ }
+ break;
+ default: /* redundant, as cmd was checked against MAXNR */
+ pr_warning(CSDIO_DEV_NAME" Redundant IOCTL\n");
+ ret = -ENOTTY;
+ }
+exit:
+ return ret;
+}
+
+static int csdio_ctrl_fasync(int fd, struct file *filp, int mode)
+{
+ pr_info(CSDIO_DEV_NAME
+ " csdio_ctrl_fasync: fd=%d, filp=%p, mode=%d\n",
+ fd, filp, mode);
+ return fasync_helper(fd, filp, mode, &g_csdio.m_async_queue);
+}
+
+/*
+ * Open and close
+ */
+static int csdio_ctrl_open(struct inode *inode, struct file *filp)
+{
+ int ret = 0;
+ struct csdio_t *csdio_ctrl_drv = NULL; /* device information */
+
+ pr_info("CSDIO ctrl open.\n");
+ csdio_ctrl_drv = container_of(inode->i_cdev, struct csdio_t, m_cdev);
+ filp->private_data = csdio_ctrl_drv; /* for other methods */
+ return ret;
+}
+
+static int csdio_ctrl_release(struct inode *inode, struct file *filp)
+{
+ pr_info("CSDIO ctrl release.\n");
+ /* remove this filp from the asynchronously notified filp's */
+ csdio_ctrl_fasync(-1, filp, 0);
+ return 0;
+}
+
+static const struct file_operations csdio_ctrl_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = csdio_ctrl_ioctl,
+ .open = csdio_ctrl_open,
+ .release = csdio_ctrl_release,
+ .fasync = csdio_ctrl_fasync,
+};
+
+static int csdio_probe(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ struct csdio_func_t *port;
+ int ret = 0;
+ struct mmc_host *host = func->card->host;
+
+ if (NULL != g_csdio.m_host && g_csdio.m_host != host) {
+ pr_info("%s: Device is on unexpected host\n",
+ CSDIO_DEV_NAME);
+ ret = -ENODEV;
+ goto exit;
+ }
+
+ /* enforce single instance policy */
+ if (g_csdio_func_table[func->num-1]) {
+ pr_err("%s - only single SDIO device supported",
+ sdio_func_id(func));
+ ret = -EEXIST;
+ goto exit;
+ }
+
+ port = kzalloc(sizeof(struct csdio_func_t), GFP_KERNEL);
+ if (!port) {
+ pr_err("Can't allocate memory\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ /* initialize SDIO side */
+ port->m_func = func;
+ sdio_set_drvdata(func, port);
+
+ pr_info("%s - SDIO device found. Function %d\n",
+ sdio_func_id(func), func->num);
+
+ port->m_device = csdio_cdev_init(&port->m_cdev, &csdio_transport_fops,
+ csdio_minor + port->m_func->num,
+ TP_DEV_NAME, &port->m_func->dev);
+
+ /* create appropriate char device */
+ if (!port->m_device)
+ goto free;
+
+ if (0 == g_csdio.m_num_of_func && NULL == host_name)
+ g_csdio.m_host = host;
+ g_csdio.m_num_of_func++;
+ g_csdio_func_table[func->num-1] = port;
+ port->m_enabled = 1;
+ goto exit;
+free:
+ kfree(port);
+exit:
+ return ret;
+}
+
+static void csdio_remove(struct sdio_func *func)
+{
+ struct csdio_func_t *port = sdio_get_drvdata(func);
+
+ csdio_transport_cleanup(port);
+ sdio_claim_host(func);
+ sdio_release_irq(func);
+ sdio_disable_func(func);
+ sdio_release_host(func);
+ kfree(port);
+ g_csdio_func_table[func->num-1] = NULL;
+ g_csdio.m_num_of_func--;
+ if (0 == g_csdio.m_num_of_func && NULL == host_name)
+ g_csdio.m_host = NULL;
+ pr_info("%s%d: Device removed (%s). Function %d\n",
+ CSDIO_DEV_NAME, func->num, sdio_func_id(func), func->num);
+}
+
+/* CONFIG_CSDIO_VENDOR_ID and CONFIG_CSDIO_DEVICE_ID are defined in Kconfig.
+ * Use kernel configuration to change the values or overwrite them through
+ * module parameters */
+static struct sdio_device_id csdio_ids[] = {
+ { SDIO_DEVICE(CONFIG_CSDIO_VENDOR_ID, CONFIG_CSDIO_DEVICE_ID) },
+ { /* end: all zeroes */},
+};
+
+MODULE_DEVICE_TABLE(sdio, csdio_ids);
+
+static struct sdio_driver csdio_driver = {
+ .probe = csdio_probe,
+ .remove = csdio_remove,
+ .name = "csdio",
+ .id_table = csdio_ids,
+};
+
+static void __exit csdio_exit(void)
+{
+ dev_t devno = MKDEV(csdio_major, csdio_minor);
+
+ sdio_unregister_driver(&csdio_driver);
+ sysfs_remove_group(&g_csdio.m_device->kobj, &dev_attr_grp);
+ kfree(g_sdio_buffer);
+ device_destroy(g_csdio.m_driver_class, devno);
+ cdev_del(&g_csdio.m_cdev);
+ class_destroy(g_csdio.m_driver_class);
+ unregister_chrdev_region(devno, csdio_transport_nr_devs);
+ pr_info("%s: Exit driver module\n", CSDIO_DEV_NAME);
+}
+
+static char *csdio_devnode(struct device *dev, mode_t *mode)
+{
+ *mode = CSDIO_DEV_PERMISSIONS;
+ return NULL;
+}
+
+static int __init csdio_init(void)
+{
+ int ret = 0;
+ dev_t devno = 0;
+
+ pr_info("Init CSDIO driver module.\n");
+
+ /* Get a range of minor numbers to work with, asking for a dynamic */
+ /* major unless directed otherwise at load time. */
+ if (csdio_major) {
+ devno = MKDEV(csdio_major, csdio_minor);
+ ret = register_chrdev_region(devno, csdio_transport_nr_devs,
+ CSDIO_DEV_NAME);
+ } else {
+ ret = alloc_chrdev_region(&devno, csdio_minor,
+ csdio_transport_nr_devs, CSDIO_DEV_NAME);
+ csdio_major = MAJOR(devno);
+ }
+ if (ret < 0) {
+ pr_err("CSDIO: can't get major %d\n", csdio_major);
+ goto exit;
+ }
+ pr_info("CSDIO char driver major number is %d\n", csdio_major);
+
+ /* kernel module got parameters: overwrite vendor and device id's */
+ if ((csdio_vendor_id != 0) && (csdio_device_id != 0)) {
+ csdio_ids[0].vendor = (u16)csdio_vendor_id;
+ csdio_ids[0].device = (u16)csdio_device_id;
+ }
+
+ /* prepare create /dev/... instance */
+ g_csdio.m_driver_class = class_create(THIS_MODULE, CSDIO_DEV_NAME);
+ if (IS_ERR(g_csdio.m_driver_class)) {
+ ret = -ENOMEM;
+ pr_err(CSDIO_DEV_NAME " class_create failed\n");
+ goto unregister_region;
+ }
+ g_csdio.m_driver_class->devnode = csdio_devnode;
+
+ /* create CSDIO ctrl driver */
+ g_csdio.m_device = csdio_cdev_init(&g_csdio.m_cdev,
+ &csdio_ctrl_fops, csdio_minor, CSDIO_DEV_NAME, NULL);
+ if (!g_csdio.m_device) {
+ pr_err("%s: Unable to create ctrl driver\n",
+ CSDIO_DEV_NAME);
+ goto destroy_class;
+ }
+
+ g_sdio_buffer = kmalloc(CSDIO_SDIO_BUFFER_SIZE, GFP_KERNEL);
+ if (!g_sdio_buffer) {
+ pr_err("Unable to allocate %d bytes\n", CSDIO_SDIO_BUFFER_SIZE);
+ ret = -ENOMEM;
+ goto destroy_cdev;
+ }
+
+ ret = sysfs_create_group(&g_csdio.m_device->kobj, &dev_attr_grp);
+ if (ret) {
+ pr_err("%s: Unable to create device attribute\n",
+ CSDIO_DEV_NAME);
+ goto free_sdio_buff;
+ }
+
+ g_csdio.m_num_of_func = 0;
+ g_csdio.m_host = NULL;
+
+ if (NULL != host_name) {
+ struct device *dev = bus_find_device_by_name(&platform_bus_type,
+ NULL, host_name);
+ if (NULL != dev) {
+ g_csdio.m_host = dev_get_drvdata(dev);
+ } else {
+ pr_err("%s: Host '%s' doesn't exist!\n", CSDIO_DEV_NAME,
+ host_name);
+ }
+ }
+
+ pr_info("%s: Match with VendorId=0x%X, DeviceId=0x%X, Host = %s\n",
+ CSDIO_DEV_NAME, csdio_device_id, csdio_vendor_id,
+ (NULL == host_name) ? "Any" : host_name);
+
+ /* register sdio driver */
+ ret = sdio_register_driver(&csdio_driver);
+ if (ret) {
+ pr_err("%s: Unable to register as SDIO driver\n",
+ CSDIO_DEV_NAME);
+ goto remove_group;
+ }
+
+ goto exit;
+
+remove_group:
+ sysfs_remove_group(&g_csdio.m_device->kobj, &dev_attr_grp);
+free_sdio_buff:
+ kfree(g_sdio_buffer);
+destroy_cdev:
+ cdev_del(&g_csdio.m_cdev);
+destroy_class:
+ class_destroy(g_csdio.m_driver_class);
+unregister_region:
+ unregister_chrdev_region(devno, csdio_transport_nr_devs);
+exit:
+ return ret;
+}
+module_param(csdio_vendor_id, uint, S_IRUGO);
+module_param(csdio_device_id, uint, S_IRUGO);
+module_param(host_name, charp, S_IRUGO);
+
+module_init(csdio_init);
+module_exit(csdio_exit);
+
+MODULE_AUTHOR("Code Aurora Forum");
+MODULE_DESCRIPTION("CSDIO device driver version " VERSION);
+MODULE_VERSION(VERSION);
+MODULE_LICENSE("GPL v2");
@@ -0,0 +1,37 @@
+#ifndef CSDIO_H
+#define CSDIO_H
+
+#include <linux/ioctl.h>
+
+#define CSDIO_IOC_MAGIC 'm'
+
+#define CSDIO_IOC_ENABLE_HIGHSPEED_MODE _IO(CSDIO_IOC_MAGIC, 0)
+#define CSDIO_IOC_SET_DATA_TRANSFER_CLOCKS _IO(CSDIO_IOC_MAGIC, 1)
+#define CSDIO_IOC_SET_OP_CODE _IO(CSDIO_IOC_MAGIC, 2)
+#define CSDIO_IOC_FUNCTION_SET_BLOCK_SIZE _IO(CSDIO_IOC_MAGIC, 3)
+#define CSDIO_IOC_SET_BLOCK_MODE _IO(CSDIO_IOC_MAGIC, 4)
+#define CSDIO_IOC_CONNECT_ISR _IO(CSDIO_IOC_MAGIC, 5)
+#define CSDIO_IOC_DISCONNECT_ISR _IO(CSDIO_IOC_MAGIC, 6)
+#define CSDIO_IOC_CMD52 _IO(CSDIO_IOC_MAGIC, 7)
+#define CSDIO_IOC_CMD53 _IO(CSDIO_IOC_MAGIC, 8)
+#define CSDIO_IOC_ENABLE_ISR _IO(CSDIO_IOC_MAGIC, 9)
+#define CSDIO_IOC_DISABLE_ISR _IO(CSDIO_IOC_MAGIC, 10)
+#define CSDIO_IOC_SET_VDD _IO(CSDIO_IOC_MAGIC, 11)
+#define CSDIO_IOC_GET_VDD _IO(CSDIO_IOC_MAGIC, 12)
+
+#define CSDIO_IOC_MAXNR 12
+
+struct csdio_cmd53_ctrl_t {
+ uint32_t m_block_mode; /* data tran. byte(0)/block(1) mode */
+ uint32_t m_op_code; /* address auto increment flag */
+ uint32_t m_address;
+} __attribute__ ((packed));
+
+struct csdio_cmd52_ctrl_t {
+ uint32_t m_write;
+ uint32_t m_address;
+ uint32_t m_data;
+ uint32_t m_ret;
+} __attribute__ ((packed));
+
+#endif
@@ -86,6 +86,7 @@ header-y += coff.h
header-y += connector.h
header-y += const.h
header-y += cramfs_fs.h
+header-y += csdio.h
header-y += cuda.h
header-y += cyclades.h
header-y += cycx_cfm.h