/* * w25q spi flash serial driver * * Copyright (C) 2013 BLX IC Design Corp.,Ltd. * Author: Davied, apple_guet@126.com * */ #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_SPI_FLASH_W25Q_DEBUG #define DBG(msg...) do { \ printk(KERN_INFO msg); \ } while (0) #else #define DBG(msg...) do { } while(0) #endif #define W25Q_BIT_LOCK_OPEN 0x00 /* chip command */ #define W25X_WRITE_STAT_REG 0x01 #define W25X_PAGE_PROG 0x02 #define W25X_READ_DATA 0x03 #define W25X_WRITE_DISABLE 0x04 #define W25X_READ_STAT_REG 0x05 #define W25X_WRITE_ENABLE 0x06 #define W25X_FAST_READ_DATE 0x0B #define W25X_SECTOR_ERASE_CMD 0x20 #define W25X_FastReadDual 0x3B #define W25X_HALF_BLOCK_ERASE_CMD 0x52 #define W25X_READ_ID_CMD 0x90 #define W25X_BLOCK_ERASE_CMD 0xD8 #define W25X_CHIP_ERASE 0xC7 #define W25X_POWER_DOWN 0xB9 #define W25X_ReleasePowerDown 0xAB #define W25X_DeviceID 0xAB #define W25X_JedecDeviceID 0x9F /* ioctl command */ #define W25Q_IOC_MAGIC 'w' #define W25Q_IOC_MAXNR 5 #define W25Q_SECTOR_ERASE _IOW(W25Q_IOC_MAGIC, 0, int) #define W25Q_HALF_BLOCK_ERASE _IOW(W25Q_IOC_MAGIC, 1, int) #define W25Q_BLOCK_ERASE _IOW(W25Q_IOC_MAGIC, 2, int) #define W25Q_CHIP_ERASE _IOW(W25Q_IOC_MAGIC, 3, int) #define W25Q_READ_DEVICE_ID _IOR(W25Q_IOC_MAGIC, 4, int) /* erase addr */ #define W25Q_ONE_SECTOR_ADDR (0x01 << 12) #define W25Q_HALF_BLOCK_ADDR (0x01 << 15) #define W25Q_ONE_BLOCK_ADDR (0x01 << 16) /* oper num */ #define W25Q_SECTOR_MAX 1023 #define W25Q_HALF_BLOCK_MAX 127 #define W25Q_BLOCK_MAX 63 /* 16 spi flash should be enough for everyone */ #define W25Q_MAX_MINOR 16 #define W25Q_SPI_FLASH_NAME "w25q-spi-flash" #define W25Q_BUF_LEN W25Q_ONE_SECTOR_ADDR #define W25Q_PAGE_BYTES 256 struct w25q_dev { unsigned suspend_flg:1; u8 cmd; u8 name[20]; u16 len; u32 addr; u32 result; u32 const_addr; dev_t devt; unsigned long bit_lock; u8 *buf; /* tx and rx buf point */ struct cdev cdev; struct class *class; struct device *dev; struct mutex mlock; struct spi_device *spi; }; static void w25q_write_enable(struct w25q_dev *w25q) { u8 cmd = W25X_WRITE_ENABLE; spi_w8r8(w25q->spi, cmd); } static int w25q_write_read_reg(struct w25q_dev *w25q, u8 *cmd) { int ret = 0; struct spi_transfer x[4]; struct spi_message message; u8 i = 0, rx = 0, dum_value = 0xff; spi_message_init(&message); memset(x, 0, sizeof x); for (i = 0; i < 4; i++) { x[i].len = 1; spi_message_add_tail(&x[i], &message); } x[0].tx_buf = cmd; x[1].rx_buf = ℞ x[2].tx_buf = &dum_value; x[3].rx_buf = ℞ /* do the i/o */ ret = spi_sync(w25q->spi, &message); if (ret == 0) return rx; else { dev_err(w25q->dev, "%s:spi_sync err(%d)!!!!\n", __func__, ret); return -EIO; } } static u8 w25q_read_stat_reg(struct w25q_dev *w25q) { int ret = 0; u8 cmd = W25X_READ_STAT_REG; ret = w25q_write_read_reg(w25q, &cmd); if (ret < 0) { dev_err(w25q->dev, "%s:w25q_write_read_reg err(%d)!!!!\n", __func__, ret); return 0xff; //error } else return (u8)ret; } static int w25q_wait_null(struct w25q_dev *w25q) { uint8_t limit = 500; /* wait BUSY bit clear */ while(((w25q_read_stat_reg(w25q) & 0x01) == 0x01) && (limit != 0)) { limit--; mdelay(5); } if (limit == 0) { dev_err(w25q->dev, "%s:time out!!!!\n", __func__); return -EBUSY; } else return 0; } /* * when you call this function, * the w25q->cmd, w25q->len(receive len) * w25q->buf(kzalloc receive) and w25q->addr are OK * */ static int w25q_read_data(struct w25q_dev *w25q) { int ret = 0; struct spi_message msg; struct spi_transfer x[(w25q->len + 4) * 2]; u8 i = 0, rx = 0, dumy_value = 0xff, tx_buff[4] = {0}; w25q_write_enable(w25q); //SET WEL ret = w25q_wait_null(w25q); if (ret != 0) { dev_err(w25q->dev, "%s:w25q_wait_null err(%d)!!!!\n", __func__, ret); return ret; } if((w25q_read_stat_reg(w25q) & 0x02) != 0x02) { dev_err(w25q->dev, "%s:w25q_read_stat_reg err!!!!\n", __func__); return -EBUSY; //disable write } DBG("cmd = 0x%x, addr = 0x%x\n", w25q->cmd, w25q->addr); tx_buff[0] = w25q->cmd; tx_buff[1] = ((u8)(w25q->addr >> 16)); tx_buff[2] = ((u8)(w25q->addr >> 8)); tx_buff[3] = ((u8)(w25q->addr)); spi_message_init(&msg); memset(x, 0, sizeof x); for (i = 0; i < 8; i++) { //cmd x[i].len = 1; spi_message_add_tail(&x[i], &msg); if ((i % 2) == 0) { x[i].tx_buf = &tx_buff[i / 2]; } else { x[i].rx_buf = ℞ } } for (i = 8; i < (w25q->len + 4) * 2; i++) { x[i].len = 1; spi_message_add_tail(&x[i], &msg); if ((i % 2) == 0) { x[i].tx_buf = &dumy_value; } else { x[i].rx_buf = w25q->buf++; } } /* do the i/o */ return spi_sync(w25q->spi, &msg); } static int w25q_open(struct inode *inode, struct file *file) { struct w25q_dev *w25q = container_of(inode->i_cdev, struct w25q_dev, cdev); if (test_and_set_bit(W25Q_BIT_LOCK_OPEN, &w25q->bit_lock)) { DBG("!!!!w25q open err, busy!\n"); return -EBUSY; } file->private_data = w25q; return 0; } /* * when you call this function, * the w25q->cmd, w25q->len(tx date len), * w25q->addr and w25q->buf(date) are OK * */ static int w25q_write_date(struct w25q_dev *w25q) { int ret = 0; u8 i = 0, rx = 0; struct spi_message message; struct spi_transfer x[(w25q->len + 4) * 2]; w25q_write_enable(w25q); //SET WEL ret = w25q_wait_null(w25q); if (ret != 0) { dev_err(w25q->dev, "%s:wait null err(%d)!!!!\n", __func__, ret); return ret; } if((w25q_read_stat_reg(w25q) & 0x02) != 0x02) { dev_err(w25q->dev, "%s:w25q_read_stat_reg err!!!!\n", __func__); return -EBUSY; //disable write } DBG("cmd = 0x%x, addr = 0x%x\n", w25q->cmd, w25q->addr); w25q->buf[0] = w25q->cmd; w25q->buf[1] = ((u8)(w25q->addr >> 16)); w25q->buf[2] = ((u8)(w25q->addr >> 8)); w25q->buf[3] = ((u8)w25q->addr); spi_message_init(&message); memset(x, 0, sizeof x); for (i = 0; i < (w25q->len + 4) * 2; i++) { x[i].len = 1; spi_message_add_tail(&x[i], &message); if ((i % 2) == 0) { x[i].tx_buf = w25q->buf++; } else { x[i].rx_buf = ℞ } } /* do the i/o */ ret = spi_sync(w25q->spi, &message); if (ret != 0) { dev_err(w25q->dev, "%s:spi_sync err(%d)!!!!\n", __func__, ret); return ret; } ret = w25q_wait_null(w25q); if (ret != 0) dev_err(w25q->dev, "%s:err(%d)!!!!\n", __func__, ret); return ret; } static int w25q_erase(struct w25q_dev *w25q, u32 num, unsigned int cmd) { int ret = 0; u8 *buf_start = NULL; switch(cmd) { case W25Q_SECTOR_ERASE: DBG("sector erase cmd\n"); if (num > W25Q_SECTOR_MAX) { dev_err(w25q->dev, "%s:sector max is over!!!!\n", __func__); return -EFAULT; } w25q->const_addr = num * W25Q_ONE_SECTOR_ADDR; w25q->cmd = W25X_SECTOR_ERASE_CMD; break; case W25Q_HALF_BLOCK_ERASE: DBG("half block erase cmd\n"); if (num > W25Q_HALF_BLOCK_MAX) { dev_err(w25q->dev, "%s:half block max is over!!!!\n", __func__); return -EFAULT; } w25q->const_addr = num * W25Q_HALF_BLOCK_ADDR; w25q->cmd = W25X_HALF_BLOCK_ERASE_CMD; break; case W25Q_BLOCK_ERASE: DBG("block erase cmd\n"); if (num > W25Q_BLOCK_MAX) { dev_err(w25q->dev, "%s:block max is over!!!!\n", __func__); return -EFAULT; } w25q->const_addr = num * W25Q_ONE_BLOCK_ADDR; w25q->cmd = W25X_BLOCK_ERASE_CMD; break; } DBG("w25q->const_addr = 0x%x\n", w25q->const_addr); w25q->len = 0; buf_start = w25q->buf = kzalloc(w25q->len + 4, GFP_KERNEL); if (!buf_start) { dev_err(w25q->dev, "%s:kzalloc is err(%d)!!!!\n", __func__, ret); return -ENOMEM; } w25q->addr = w25q->const_addr; ret = w25q_write_date(w25q); kfree(buf_start); if (ret != 0) { dev_err(w25q->dev, "%s:w25q_write_date err(%d)!!!!\n", __func__, ret); return ret; } DBG("%s:erase OK\n", __func__); return ret; } static int w25q_chip_erase(struct w25q_dev *w25q) { int ret = 0; u8 cmd = W25X_CHIP_ERASE; DBG("w25q_chip_erase\n"); w25q_write_enable(w25q); //SET WEL ret = w25q_wait_null(w25q); if (ret != 0) { dev_err(w25q->dev, "%s:w25q_wait_null err(%d)!!!!\n", __func__, ret); return ret; } if((w25q_read_stat_reg(w25q) & 0x02) != 0x02) { dev_err(w25q->dev, "%s:w25q_read_stat_reg err(%d)!!!!\n", __func__, ret); return -EBUSY; //disable write } spi_w8r8(w25q->spi, cmd); return w25q_wait_null(w25q); } static int w25q_read_id(struct w25q_dev *w25q) { int ret = 0; u8 *data_buf_start; DBG("%s:start.\n", __func__); w25q->len = 2; w25q->addr = 0; w25q->cmd = W25X_READ_ID_CMD; data_buf_start = w25q->buf = kzalloc(w25q->len, GFP_KERNEL); if (!data_buf_start) { dev_err(w25q->dev, "%s:kzalloc err(%d)!!!!\n", __func__, ret); return -ENOMEM; } ret = w25q_read_data(w25q); w25q->buf = data_buf_start; w25q->result = *w25q->buf << 8; w25q->buf++; w25q->result |= *w25q->buf; kfree(data_buf_start); if (ret != 0) { dev_err(w25q->dev, "%s:w25q_read_data err(%d)!!!!\n", __func__, ret); return ret; } DBG("%s:read id OK.\n", __func__); return ret; } static long w25q_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; u32 get_value = 0; struct w25q_dev *w25q= file->private_data; void __user *argp = (void __user *)arg; int __user *p = argp; DBG("@@@@w25q ioctl start.\n"); ret = mutex_lock_interruptible(&w25q->mlock); if (ret) { dev_err(w25q->dev, "%s:mutex_lock_interruptible err(%d)!!!!\n", __func__, ret); return ret; } if ((_IOC_TYPE(cmd) != W25Q_IOC_MAGIC) || (_IOC_NR(cmd) > W25Q_IOC_MAXNR)) { dev_err(w25q->dev, "%s:ioc type or ioc nr err!!!!\n", __func__); ret = -ENOTTY; goto exit; } switch(cmd) { case W25Q_SECTOR_ERASE: case W25Q_HALF_BLOCK_ERASE: case W25Q_BLOCK_ERASE: if (get_user(get_value, p)) { dev_err(w25q->dev, "%s:get_user err!!!!\n", __func__); ret = -EFAULT; goto exit; } ret = w25q_erase(w25q, get_value, cmd); break; case W25Q_CHIP_ERASE: ret = w25q_chip_erase(w25q); break; case W25Q_READ_DEVICE_ID: ret = w25q_read_id(w25q); if (ret == 0) put_user(w25q->result, p); break; default: dev_err(w25q->dev, "%s:cmd error!!!!\n", __func__); ret = -ENOTTY; break; } DBG("w25q ioctl success.\n"); exit: mutex_unlock(&w25q->mlock); return ret; } static ssize_t w25q_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { int ret = 0; u32 buf_size = 0, read_len = 0; struct w25q_dev *w25q = file->private_data; u8 *data_buf_start = NULL, *data_buf_pos = NULL, *w25q_buf_start = NULL; DBG("@@@@w25q read start\n"); data_buf_start = data_buf_pos = kzalloc(W25Q_BUF_LEN, GFP_KERNEL); w25q_buf_start = w25q->buf = kzalloc(W25Q_PAGE_BYTES, GFP_KERNEL); if (!data_buf_start || !w25q_buf_start ) { dev_err(w25q->dev, "%s:kzalloc err(%d)!!!!\n", __func__, ret); return -ENOMEM; } ret = mutex_lock_interruptible(&w25q->mlock); if (ret) { dev_err(w25q->dev, "%s:mutex_lock_interruptible err(%d)!!!!\n", __func__, ret); goto exit_kfree; } read_len = W25Q_BUF_LEN; buf_size = min(count, read_len); read_len = buf_size; w25q->cmd = W25X_READ_DATA; w25q->addr = w25q->const_addr; DBG("w25q->addr = 0x%x\n", w25q->addr); while (buf_size) { w25q->buf = w25q_buf_start; w25q->len = min(buf_size, (u32)W25Q_PAGE_BYTES); ret = w25q_read_data(w25q); if (ret != 0) { dev_err(w25q->dev, "%s:w25q_read_data err(%d)!!!!\n", __func__, ret); goto exit_lock; } memcpy(data_buf_pos, w25q_buf_start, w25q->len); data_buf_pos += w25q->len; buf_size -= w25q->len; w25q->addr += w25q->len; } ret = copy_to_user(user_buf, data_buf_start, read_len); ret = read_len -ret; DBG("%s:success\n", __func__); exit_lock: mutex_unlock(&w25q->mlock); exit_kfree: kfree(data_buf_start); kfree(w25q_buf_start); return ret; } static ssize_t w25q_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { int ret = 0; u32 buf_size = 0, len = 0; struct w25q_dev *w25q= file->private_data; u8 *data_buf_start = NULL, *data_buf_pos = NULL, *w25q_buf_start = NULL; DBG("@@@@w25q write start\n"); data_buf_start = data_buf_pos = kzalloc(W25Q_BUF_LEN, GFP_KERNEL); w25q_buf_start = w25q->buf = kzalloc(W25Q_PAGE_BYTES + 4, GFP_KERNEL); if (!data_buf_start || !w25q_buf_start) { dev_err(w25q->dev, "%s:kzalloc error(%d)!!!!\n", __func__, ret); return -ENOMEM; } ret = mutex_lock_interruptible(&w25q->mlock); if (ret) { dev_err(w25q->dev, "%s:mutex lock error(%d)!!!!\n", __func__, ret); goto exit_kfree; } len = W25Q_BUF_LEN; buf_size = min(count, len); if (copy_from_user(data_buf_pos, user_buf, buf_size)) { dev_err(w25q->dev, "%s:copy_from_user error(%d)!!!!\n", __func__, ret); ret = -EFAULT; goto exit_lock; } DBG("w25q->const_addr = 0x%x\n", w25q->const_addr); data_buf_pos = data_buf_start; w25q->cmd = W25X_PAGE_PROG; w25q->addr = w25q->const_addr; while(buf_size) { w25q->buf = w25q_buf_start; w25q->len = min(buf_size, (u32)W25Q_PAGE_BYTES); memcpy(w25q->buf + 4, data_buf_pos, w25q->len); ret = w25q_write_date(w25q); if (ret != 0) { dev_err(w25q->dev, "%s:w25q_write_date error(%d)!!!!\n", __func__, ret); break; } data_buf_pos += w25q->len; w25q->addr += w25q->len; buf_size -= w25q->len; } DBG("w25q write success.\n"); exit_lock: mutex_unlock(&w25q->mlock); exit_kfree: kfree(data_buf_start); kfree(w25q_buf_start); return ret; } static int w25q_release(struct inode *inode, struct file *file) { int ret = 0; struct w25q_dev *w25q= file->private_data; clear_bit_unlock(W25Q_BIT_LOCK_OPEN, &w25q->bit_lock); return ret; } static loff_t w25q_llseek(struct file *file, loff_t offset, int origin) { return 0; } static const struct file_operations w25q_fops = { .owner = THIS_MODULE, .open = w25q_open, .write = w25q_write, .unlocked_ioctl = w25q_ioctl, .read = w25q_read, .llseek = w25q_llseek, .release = w25q_release, }; static int __devinit w25q_probe(struct spi_device *spi) { int ret = 0; struct w25q_dev *w25q = NULL; DBG("############\n"); printk(KERN_INFO "w25q spi flash probe start.\n"); w25q = kzalloc(sizeof(struct w25q_dev), GFP_KERNEL); if (!w25q) { dev_err(&spi->dev, "%s:kzalloc error(%d)!!!!\n", __func__, ret); return -ENOMEM; } w25q->spi = spi; w25q->suspend_flg = 0; w25q->dev = &spi->dev; mutex_init(&w25q->mlock); strlcpy(w25q->name, W25Q_SPI_FLASH_NAME, sizeof(w25q->name)); ret = alloc_chrdev_region(&w25q->devt, 0, W25Q_MAX_MINOR, "w25q"); if (ret < 0) { dev_err(w25q->dev, "%s:failed to allocate char dev region(%d)!!!!\n", __func__, ret); goto err_kzall; } w25q->dev->devt = MKDEV(MAJOR(w25q->devt), 1); cdev_init(&w25q->cdev, &w25q_fops); w25q->cdev.owner = THIS_MODULE; ret = cdev_add(&w25q->cdev, w25q->devt, 1); if (ret) { dev_err(w25q->dev, "%s:cdev add error(%d)!!!!\n", __func__, ret); goto err_alloc; } w25q->class = class_create(THIS_MODULE, "w25q-spi"); if (IS_ERR(w25q->class)) { dev_err(w25q->dev, "%s:failed in create w25q spi flash class(%d)!!!!\n", __func__, ret); goto err_alloc; } device_create(w25q->class, NULL, w25q->devt, NULL, "w25q"); dev_set_drvdata(&spi->dev, w25q); #ifdef CONFIG_PM /* set pm runtime power state and register with power system */ pm_runtime_set_active(w25q->dev); pm_runtime_enable(w25q->dev); pm_runtime_resume(w25q->dev); //device_init_wakeup(gsc->dev, 1); #endif printk(KERN_INFO "w25q spi flash probe success.\n"); DBG("############\n"); return 0; err_alloc: unregister_chrdev_region(w25q->devt, W25Q_MAX_MINOR); err_kzall: kfree(w25q); printk(KERN_ERR "!!!!!!w25q spi flash probe error!!!!!!\n"); return ret; } static int __devexit w25q_remove(struct spi_device *spi) { struct w25q_dev *w25q = dev_get_drvdata(&spi->dev); cdev_del(&w25q->cdev); unregister_chrdev_region(w25q->devt, W25Q_MAX_MINOR); device_destroy(w25q->class, w25q->devt); class_destroy(w25q->class); kfree(w25q); return 0; } static void w25q_shutdown(struct spi_device *spi) { //struct w25q_dev *w25q = dev_get_drvdata(&spi->dev); printk(KERN_INFO "%s start\n", __func__); } #ifdef CONFIG_PM static int w25q_suspend(struct device *dev) { struct spi_device *spi = to_spi_device(dev); struct w25q_dev *w25q = dev_get_drvdata(&spi->dev); printk(KERN_INFO "%s start\n", __func__); if (w25q->suspend_flg == 1) { } else { w25q->suspend_flg = 1; } return 0; } static int w25q_resume(struct device *dev) { #if 0 struct spi_device *spi = to_spi_device(dev); struct w25q_dev *w25q = dev_get_drvdata(&spi->dev); #endif printk(KERN_INFO "%s start\n", __func__); return 0; } #else #define w25q_suspend NULL #define w25q_resume NULL #endif //end #ifdef CONFIG_PM static const struct dev_pm_ops w25q_pm_ops = { .suspend = w25q_suspend, .resume = w25q_resume, .runtime_suspend = w25q_suspend, .runtime_resume = w25q_resume, }; static struct spi_driver w25q_driver = { //.id_table = w25q_ids, .probe = w25q_probe, .remove = __devexit_p(w25q_remove), .shutdown = w25q_shutdown, .driver = { .owner = THIS_MODULE, .name = "spi-w25q", .pm = &w25q_pm_ops, }, }; static int __init w25q_module_init(void) { return spi_register_driver(&w25q_driver); } static void __exit w25q_module_exit(void) { spi_unregister_driver(&w25q_driver); } module_init(w25q_module_init); module_exit(w25q_module_exit); MODULE_DESCRIPTION("w25q spi flash"); MODULE_AUTHOR("Davied"); MODULE_LICENSE("GPL"); MODULE_ALIAS("spi driver:w25q");