mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
04e554807c
Change-Id: Ibead64ce2e901dede2ddd1b86088b88f2350ce92 Signed-off-by: Duy Truong <dtruong@codeaurora.org>
1074 lines
26 KiB
C
1074 lines
26 KiB
C
/*
|
|
* 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");
|