/* * 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 #include #include #include #include #include #include #include #include #include #include /* Char device */ #include #include /* Sdio device */ #include #include #include #include #include #include #include #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");