blob: 85306d35fe07d9932c506c2e179091cdda537ed9 [file] [log] [blame]
/*
* Copyright (c) 2010, The Linux Foundation. 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 FALSE 0
#define TRUE 1
#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:%d (Bl:%d, 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 (%d)"
"(Md:%d, Addr:0x%04X, Sz:%d)\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 (%d)"
"(Md:%d, Addr:0x%04X, Sz:%d)\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 int csdio_transport_ioctl(struct inode *inode,
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=%x\n",
func->num, (unsigned int)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,
.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 int csdio_ctrl_ioctl(struct inode *inode, 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,
.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 = TRUE;
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("The Linux Foundation");
MODULE_DESCRIPTION("CSDIO device driver version " VERSION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL v2");