diff options
author | Todd Doucet <todd.doucet@timesys.com> | 2010-02-03 17:06:33 -0500 |
---|---|---|
committer | Todd Doucet <todd.doucet@timesys.com> | 2010-02-03 17:06:33 -0500 |
commit | ff238a4df84428befc55d49f58864dfc47ff853d (patch) | |
tree | 8796b828b396f5b7ed017904ff77b614dbf38677 /drivers/mtd/nand/lba/lba-core.c | |
parent | 4a6908a3a050aacc9c3a2f36b276b46c0629ad91 (diff) |
Kernel as received from Digi for their Wi-Mx51 SoC running on their
CCWMX51JS carrier board.
Diffstat (limited to 'drivers/mtd/nand/lba/lba-core.c')
-rw-r--r-- | drivers/mtd/nand/lba/lba-core.c | 619 |
1 files changed, 619 insertions, 0 deletions
diff --git a/drivers/mtd/nand/lba/lba-core.c b/drivers/mtd/nand/lba/lba-core.c new file mode 100644 index 000000000000..cdf28ca42b2a --- /dev/null +++ b/drivers/mtd/nand/lba/lba-core.c @@ -0,0 +1,619 @@ +/* + * Freescale STMP37XX/STMP378X LBA/core driver + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/dma-mapping.h> +#include <linux/ctype.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <mach/stmp3xxx.h> +#include <mach/dma.h> +#include "gpmi.h" +#include "lba.h" + +#define LBA_SELFPM_TIMEOUT 2000 /* msecs */ +dma_addr_t g_cmd_handle; +dma_addr_t g_data_handle; +uint8_t *g_data_buffer; +uint8_t *g_cmd_buffer; + +uint8_t lba_get_status1(void *priv) +{ + uint8_t cmd_buf[] = { 0x70 } ; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_W4R}, + {0, F_DATA_READ | FE_END}, + }; + *g_data_buffer = 0; + queue_cmd(priv, cmd_buf, 0, 1, g_data_handle, 1, lba_flags); + queue_run(priv); + return *g_data_buffer; +} + + +int lba_wait_for_ready(void *priv) +{ + int stat; + unsigned long j_start = jiffies; + + stat = lba_get_status1(priv); + if ((stat & 0x60) != 0x60) { + while (((stat & 0x60) != 0x60) && + (jiffies - j_start < msecs_to_jiffies(2000))) { + schedule(); + stat = lba_get_status1(priv); + } + } + if (stat != 0x60) + return stat; + + return 0; +} + +int lba_write_sectors(void *priv, unsigned int sector, unsigned int count, + void *buffer, dma_addr_t handle) +{ + uint8_t cmd_buf[] = { + 0x80, + count & 0xff, (count >> 8) & 0xff, /* Count */ + (sector & 0xff), (sector >> 8) & 0xff, /* Address */ + (sector >> 16) & 0xff, (sector >> 24) & 0xff, /* Addres */ + /* Data goes here */ + 0x10 + }; + + struct lba_cmd flags_t1[] = { /* Transmition mode 1/A */ + {7 , F_CMD | FE_CMD_INC | FE_W4R}, + {0, F_DATA_WRITE}, + {1 , F_CMD | FE_END} + }; + + if (count > 8) + return -EINVAL; + + if (lba_wait_for_ready(priv)) + return -EIO; + + while (count) { + int cnt = (count < 8) ? count : 8; + int data_len = cnt * 512; + + queue_cmd(priv, cmd_buf, 0, 8, + handle, data_len, flags_t1); + + handle += data_len; + count -= cnt; + + } + + queue_run(priv); + + return count; + +} + +int lba_read_sectors(void *priv, unsigned int sector, unsigned int count, + void *buffer, dma_addr_t handle) +{ + + int data_len; + int cnt; + uint8_t cmd_buf[] = { + 0x00, + count & 0xff, (count >> 8) & 0xff, /* Count */ + (sector & 0xff), (sector >> 8) & 0xff, /* Addr */ + (sector >> 16) & 0xff, (sector >> 24) & 0xff, /* Addr */ + 0x30 + /* Data goes here <data> */ + + }; + + struct lba_cmd flags_r3[] = { /* Read mode 3/A */ + {7 , F_CMD | FE_CMD_INC | FE_W4R}, + {1 , F_CMD }, + {0 , F_DATA_READ | FE_W4R | FE_END }, + }; + struct lba_cmd flags_r3c[] = { /* Read mode 3/A */ + {0 , F_DATA_READ | FE_W4R | FE_END }, + }; + struct lba_cmd *flags = flags_r3; + int flags_len = 8; + + if (count > 8) + return -EINVAL; + + if (lba_wait_for_ready(priv)) + return -EIO; + + while (count) { + cnt = (count < 8) ? count : 8; + data_len = cnt * 512; + queue_cmd(priv, cmd_buf, 0, flags_len, handle, data_len, flags); + handle += data_len; + count -= cnt; + flags = flags_r3c; + flags_len = 0; + } + + queue_run(priv); + + return count; + +} + + +uint8_t lba_get_id1(void *priv, uint8_t *ret_buffer) +{ + uint8_t cmd_buf[] = { 0x90 , 0x00, /* Data read 5bytes*/ }; + struct lba_cmd lba_flags[] = { + {2 , F_CMD | FE_CMD_INC | FE_W4R}, + {0, F_DATA_READ | FE_END}, + }; + + queue_cmd(priv, cmd_buf, 0, 2, g_data_handle, 5, lba_flags); + queue_run(priv); + memcpy(ret_buffer, g_data_buffer, 5); + + return 0; +} + +uint8_t lba_get_id2(void *priv, uint8_t *ret_buffer) +{ + uint8_t cmd_buf[] = { 0x92 , 0x00, /* Data read 5bytes*/ }; + struct lba_cmd lba_flags[] = { + {2 , F_CMD | FE_CMD_INC | FE_W4R}, + {0, F_DATA_READ | FE_END}, + }; + + queue_cmd(priv, cmd_buf, 0, 2, g_data_handle, 5, lba_flags); + queue_run(priv); + memcpy(ret_buffer, g_data_buffer, 5); + return 0; +} + +uint8_t lba_get_status2(void *priv) +{ + uint8_t cmd_buf[] = { 0x71 }; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_CMD_INC | FE_W4R}, + {0 , F_DATA_READ | FE_END}, + }; + *g_data_buffer = 0; + queue_cmd(priv, cmd_buf, 0, 1, g_data_handle, 1, lba_flags); + queue_run(priv); + return *g_data_buffer; +} + +static uint8_t lba_parse_status2(void *priv) +{ + uint8_t stat; + + stat = lba_get_status2(priv); + printk(KERN_INFO "Status2:|"); + if (stat & 0x40) + printk(" C.PAR.ERR |"); /* no KERN_ here */ + if (stat & 0x20) + printk(" NO spare |"); + if (stat & 0x10) + printk(" ADDR OoRange |"); + if (stat & 0x8) + printk(" high speed |"); + if ((stat & 0x6) == 6) + printk(" MDP |"); + if ((stat & 0x6) == 4) + printk(" VFP |"); + if ((stat & 0x6) == 2) + printk(" PNP |"); + if (stat & 1) + printk(" PSW |"); + + printk("\n"); + return 0; +} + + +int lba_2mdp(void *priv) +{ + uint8_t cmd_buf[] = { 0xFC }; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_W4R | FE_END} + }; + + queue_cmd(priv, cmd_buf, 0, 1, 0, 0, lba_flags); + queue_run(priv); + return 0; +} + +void _lba_misc_cmd_set(void *priv, uint8_t *cmd_buf) +{ + struct lba_cmd lba_flags[] = { + {6 , F_CMD | FE_CMD_INC | FE_W4R }, + {1 , F_CMD | FE_END }, + }; + + queue_cmd(priv, cmd_buf, 0, 7, 0, 0, lba_flags); + queue_run(priv); +} + +uint8_t _lba_misc_cmd_get(void *priv, uint8_t *cmd_buf) +{ + struct lba_cmd lba_flags[] = { + {6 , F_CMD | FE_CMD_INC | FE_W4R }, + {1 , F_CMD }, + {0 , F_DATA_READ | FE_W4R | FE_END} + }; + + queue_cmd(priv, cmd_buf, 0, 7, g_data_handle, 1, lba_flags); + queue_run(priv); + return *g_data_buffer; +} +void lba_mdp2vfp(void *priv, uint8_t pass[2]) +{ + uint8_t cmd_buf[] = { 0x0, 0xbe, pass[0], pass[1], 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_bcm2vfp(void *priv, uint8_t pass[2]) +{ + lba_mdp2vfp(priv, pass); +} + +void lba_powersave_enable(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xba, 0, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} +void lba_powersave_disable(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xbb, 0, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_highspeed_enable(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xbc, 0, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_highspeed_disable(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xbd, 0, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_prot1_set(void *priv, uint8_t mode) +{ + uint8_t cmd_buf[] = { 0x0, 0xa2, mode, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_prot2_set(void *priv, uint8_t mode) +{ + uint8_t cmd_buf[] = { 0x0, 0xa3, mode, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +uint8_t lba_prot1_get(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xb2, 0, 0, 0, 0, 0x57 }; + return _lba_misc_cmd_get(priv, cmd_buf); +} + +uint8_t lba_prot2_get(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xb3, 0, 0, 0, 0, 0x57 }; + return _lba_misc_cmd_get(priv, cmd_buf); +} + +uint64_t lba_mdp_size_get(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xb0, 0, 0, 0, 0, 0x57 }; + struct lba_cmd lba_flags[] = { + {6 , F_CMD | FE_CMD_INC | FE_W4R }, + {1 , F_CMD }, + {0 , F_DATA_READ | FE_W4R | FE_END} + }; + + memset((void *)g_data_buffer, 0, 8); + queue_cmd(priv, cmd_buf, 0, 7, g_data_handle, 5, lba_flags); + queue_run(priv); + return le64_to_cpu(*(long long *)g_data_buffer); +} + +void lba_cache_flush(void *priv) +{ + uint8_t cmd_buf[] = { 0xF9 }; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_W4R }, + {0 , FE_W4R | FE_END} + }; + + queue_cmd(priv, cmd_buf, 0, 7, g_data_handle, 5, lba_flags); + queue_run(priv); +} + +void lba_reboot(void *priv) +{ + uint8_t cmd_buf[] = { 0xFD }; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_W4R }, + {0 , FE_W4R | FE_END} + }; + + queue_cmd(priv, cmd_buf, 0, 7, g_data_handle, 5, lba_flags); + queue_run(priv); +} + +void lba_def_state(void *priv) +{ + lba_wait_for_ready(priv); + lba_reboot(priv); + + lba_wait_for_ready(priv); + lba_parse_status2(priv); + + lba_wait_for_ready(priv); + lba_2mdp(priv); + + lba_wait_for_ready(priv); + lba_prot1_set(priv, LBA_T_SIZE8); /* 512 * 8 */ + + lba_wait_for_ready(priv); +/* Type C read; Type A write; */ + lba_prot2_set(priv, LBA_P_WRITE_A | LBA_P_READ_C); +} + +/* + * Should be called with mode locked + */ +void lba_core_setvfp_passwd(struct lba_data *data, uint8_t pass[2]) +{ + memcpy(data->pass, pass, 2); +} + +int lba_core_lock_mode(struct lba_data *data, int mode) +{ + void *priv = &data->nand; + + if (down_interruptible(&data->mode_lock)) + return -EAGAIN; + /* + * MDP and VFP are the only supported + * modes for now. + */ + if ((mode != LBA_MODE_MDP) && + (mode != LBA_MODE_VFP)) { + up(&data->mode_lock); + return -EINVAL; + } + + while ((data->mode & LBA_MODE_MASK) == LBA_MODE_SUSP) { + up(&data->mode_lock); + + if (wait_event_interruptible( + data->suspend_q, + (data->mode & LBA_MODE_MASK) != LBA_MODE_SUSP)) + return -EAGAIN; + + if (down_interruptible(&data->mode_lock)) + return -EAGAIN; + + data->last_access = jiffies; + } + + if (data->mode & LBA_MODE_SELFPM) { + queue_plug(data); + data->mode &= ~LBA_MODE_SELFPM; + } + + if (mode == data->mode) + return 0; + + /* + * mode = VFP || MDP only + * Revisit when more modes are added + */ + switch (data->mode) { + case LBA_MODE_RST: + case LBA_MODE_PNR: + case LBA_MODE_BCM: + lba_def_state(priv); + if (mode == LBA_MODE_MDP) { + data->mode = LBA_MODE_MDP; + break; + } + /*no break -> fall down to set VFP mode*/ + case LBA_MODE_MDP: + lba_wait_for_ready(priv); + lba_mdp2vfp(priv, data->pass); + data->mode = LBA_MODE_VFP; + break; + case LBA_MODE_VFP: + lba_wait_for_ready(priv); + lba_2mdp(priv); + data->mode = LBA_MODE_MDP; + break; + default: + up(&data->mode_lock); + return -EINVAL; + } + + return 0; +} + +int lba_core_unlock_mode(struct lba_data *data) +{ + data->last_access = jiffies; + up(&data->mode_lock); + wake_up(&data->selfpm_q); + return 0; +} + +static int selfpm_timeout_expired(struct lba_data *data) +{ + return jiffies_to_msecs(jiffies - data->last_access) > 2000; +} + +static int lba_selfpm_thread(void *d) +{ + struct lba_data *data = d; + + set_user_nice(current, -5); + + while (!kthread_should_stop()) { + + if (wait_event_interruptible(data->selfpm_q, + kthread_should_stop() || + !(data->mode & LBA_MODE_SELFPM))) + continue; + + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(LBA_SELFPM_TIMEOUT)); + + if (down_trylock(&data->mode_lock)) + continue; + + if (!selfpm_timeout_expired(data)) { + up(&data->mode_lock); + continue; + } + data->mode |= LBA_MODE_SELFPM; + lba_wait_for_ready((void *)data->nand); + lba_cache_flush((void *)data->nand); + queue_release(data); + up(&data->mode_lock); + + } + + return 0; +} + +int lba_core_init(struct lba_data *data) +{ + uint8_t id_buf[5]; + uint8_t capacity; + uint8_t id1_template[5] = {0x98, 0xDC, 0x00, 0x15, 0x00}; + uint8_t id2_template[5] = {0x98, 0x21, 0x00, 0x55, 0xAA}; + void *priv = (void *)data->nand; + + + g_data = data; + g_cmd_handle = queue_get_cmd_handle(priv); + g_data_handle = queue_get_data_handle(priv); + g_data_buffer = queue_get_data_ptr(priv); + g_cmd_buffer = queue_get_cmd_ptr(priv); + + spin_lock_init(&data->lock); + sema_init(&data->mode_lock, 1); + init_waitqueue_head(&data->suspend_q); + init_waitqueue_head(&data->selfpm_q); + + + lba_get_id1(data->nand, id_buf); + if (!memcmp(id_buf, id1_template, 5)) + printk(KERN_INFO + "LBA: Found LBA/SLC NAND emulated ID\n"); + else + return -ENODEV; + + lba_get_id2(data->nand, id_buf); + capacity = id_buf[2]; + id_buf[2] = 0; + + if (memcmp(id_buf, id2_template, 5)) { + printk(KERN_INFO + "LBA: Uknown LBA device\n"); + return -ENODEV; + } + printk(KERN_INFO + "LBA: Found %dGbytes LBA NAND device\n", + 1 << capacity); + + lba_wait_for_ready(priv); + lba_parse_status2(priv); + + lba_def_state(priv); + data->mode = LBA_MODE_MDP; + + g_data->pnp_size = 0xff; + g_data->vfp_size = 16384; + + lba_wait_for_ready(priv); + g_data->mdp_size = lba_mdp_size_get(priv); + + lba_wait_for_ready(priv); + /*lba_powersave_enable(priv);*/ + /*lba_highspeed_enable(priv);*/ + + lba_wait_for_ready(priv); + lba_parse_status2(priv); + + data->thread = kthread_create(lba_selfpm_thread, + data, "lba-selfpm-%d", 1); + if (IS_ERR(data->thread)) + return PTR_ERR(data->thread); + + lba_blk_init(g_data); + + wake_up_process(data->thread); + return 0; + +}; + +int lba_core_remove(struct lba_data *data) +{ + kthread_stop(data->thread); + lba_blk_remove(data); + lba_wait_for_ready((void *)data->nand); + lba_cache_flush((void *)data->nand); + return 0; +} + +int lba_core_suspend(struct platform_device *pdev, struct lba_data *data) +{ + BUG_ON((data->mode & 0xffff) == LBA_MODE_SUSP); + if (down_interruptible(&data->mode_lock)) + return -EAGAIN; + if (data->mode & LBA_MODE_SELFPM) + queue_plug(data); + + data->mode = LBA_MODE_SUSP | LBA_MODE_SELFPM; + up(&data->mode_lock); + lba_wait_for_ready((void *)data->nand); + lba_cache_flush((void *)data->nand); + return 0; +} + +int lba_core_resume(struct platform_device *pdev, struct lba_data *data) +{ + BUG_ON((data->mode & 0xffff) != LBA_MODE_SUSP); + lba_def_state((void *)data->nand); + data->last_access = jiffies; + data->mode = LBA_MODE_MDP; + wake_up(&data->suspend_q); + return 0; +} |