diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mtd/onenand/onenand_base.c | 292 |
1 files changed, 261 insertions, 31 deletions
diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c index 6e250f3a4a16..7bd6ad3ff30a 100644 --- a/drivers/mtd/onenand/onenand_base.c +++ b/drivers/mtd/onenand/onenand_base.c | |||
@@ -1,17 +1,19 @@ | |||
1 | /* | 1 | /* |
2 | * linux/drivers/mtd/onenand/onenand_base.c | 2 | * linux/drivers/mtd/onenand/onenand_base.c |
3 | * | 3 | * |
4 | * Copyright (C) 2005-2007 Samsung Electronics | 4 | * Copyright © 2005-2009 Samsung Electronics |
5 | * Copyright © 2007 Nokia Corporation | ||
6 | * | ||
5 | * Kyungmin Park <kyungmin.park@samsung.com> | 7 | * Kyungmin Park <kyungmin.park@samsung.com> |
6 | * | 8 | * |
7 | * Credits: | 9 | * Credits: |
8 | * Adrian Hunter <ext-adrian.hunter@nokia.com>: | 10 | * Adrian Hunter <ext-adrian.hunter@nokia.com>: |
9 | * auto-placement support, read-while load support, various fixes | 11 | * auto-placement support, read-while load support, various fixes |
10 | * Copyright (C) Nokia Corporation, 2007 | ||
11 | * | 12 | * |
12 | * Vishak G <vishak.g at samsung.com>, Rohit Hagargundgi <h.rohit at samsung.com> | 13 | * Vishak G <vishak.g at samsung.com>, Rohit Hagargundgi <h.rohit at samsung.com> |
13 | * Flex-OneNAND support | 14 | * Flex-OneNAND support |
14 | * Copyright (C) Samsung Electronics, 2008 | 15 | * Amul Kumar Saha <amul.saha at samsung.com> |
16 | * OTP support | ||
15 | * | 17 | * |
16 | * This program is free software; you can redistribute it and/or modify | 18 | * This program is free software; you can redistribute it and/or modify |
17 | * it under the terms of the GNU General Public License version 2 as | 19 | * it under the terms of the GNU General Public License version 2 as |
@@ -43,6 +45,18 @@ MODULE_PARM_DESC(flex_bdry, "SLC Boundary information for Flex-OneNAND" | |||
43 | " : 0->Set boundary in unlocked status" | 45 | " : 0->Set boundary in unlocked status" |
44 | " : 1->Set boundary in locked status"); | 46 | " : 1->Set boundary in locked status"); |
45 | 47 | ||
48 | /* Default OneNAND/Flex-OneNAND OTP options*/ | ||
49 | static int otp; | ||
50 | |||
51 | module_param(otp, int, 0400); | ||
52 | MODULE_PARM_DESC(otp, "Corresponding behaviour of OneNAND in OTP" | ||
53 | "Syntax : otp=LOCK_TYPE" | ||
54 | "LOCK_TYPE : Keys issued, for specific OTP Lock type" | ||
55 | " : 0 -> Default (No Blocks Locked)" | ||
56 | " : 1 -> OTP Block lock" | ||
57 | " : 2 -> 1st Block lock" | ||
58 | " : 3 -> BOTH OTP Block and 1st Block lock"); | ||
59 | |||
46 | /** | 60 | /** |
47 | * onenand_oob_128 - oob info for Flex-Onenand with 4KB page | 61 | * onenand_oob_128 - oob info for Flex-Onenand with 4KB page |
48 | * For now, we expose only 64 out of 80 ecc bytes | 62 | * For now, we expose only 64 out of 80 ecc bytes |
@@ -2591,6 +2605,208 @@ static void onenand_unlock_all(struct mtd_info *mtd) | |||
2591 | 2605 | ||
2592 | #ifdef CONFIG_MTD_ONENAND_OTP | 2606 | #ifdef CONFIG_MTD_ONENAND_OTP |
2593 | 2607 | ||
2608 | /** | ||
2609 | * onenand_otp_command - Send OTP specific command to OneNAND device | ||
2610 | * @param mtd MTD device structure | ||
2611 | * @param cmd the command to be sent | ||
2612 | * @param addr offset to read from or write to | ||
2613 | * @param len number of bytes to read or write | ||
2614 | */ | ||
2615 | static int onenand_otp_command(struct mtd_info *mtd, int cmd, loff_t addr, | ||
2616 | size_t len) | ||
2617 | { | ||
2618 | struct onenand_chip *this = mtd->priv; | ||
2619 | int value, block, page; | ||
2620 | |||
2621 | /* Address translation */ | ||
2622 | switch (cmd) { | ||
2623 | case ONENAND_CMD_OTP_ACCESS: | ||
2624 | block = (int) (addr >> this->erase_shift); | ||
2625 | page = -1; | ||
2626 | break; | ||
2627 | |||
2628 | default: | ||
2629 | block = (int) (addr >> this->erase_shift); | ||
2630 | page = (int) (addr >> this->page_shift); | ||
2631 | |||
2632 | if (ONENAND_IS_2PLANE(this)) { | ||
2633 | /* Make the even block number */ | ||
2634 | block &= ~1; | ||
2635 | /* Is it the odd plane? */ | ||
2636 | if (addr & this->writesize) | ||
2637 | block++; | ||
2638 | page >>= 1; | ||
2639 | } | ||
2640 | page &= this->page_mask; | ||
2641 | break; | ||
2642 | } | ||
2643 | |||
2644 | if (block != -1) { | ||
2645 | /* Write 'DFS, FBA' of Flash */ | ||
2646 | value = onenand_block_address(this, block); | ||
2647 | this->write_word(value, this->base + | ||
2648 | ONENAND_REG_START_ADDRESS1); | ||
2649 | } | ||
2650 | |||
2651 | if (page != -1) { | ||
2652 | /* Now we use page size operation */ | ||
2653 | int sectors = 4, count = 4; | ||
2654 | int dataram; | ||
2655 | |||
2656 | switch (cmd) { | ||
2657 | default: | ||
2658 | if (ONENAND_IS_2PLANE(this) && cmd == ONENAND_CMD_PROG) | ||
2659 | cmd = ONENAND_CMD_2X_PROG; | ||
2660 | dataram = ONENAND_CURRENT_BUFFERRAM(this); | ||
2661 | break; | ||
2662 | } | ||
2663 | |||
2664 | /* Write 'FPA, FSA' of Flash */ | ||
2665 | value = onenand_page_address(page, sectors); | ||
2666 | this->write_word(value, this->base + | ||
2667 | ONENAND_REG_START_ADDRESS8); | ||
2668 | |||
2669 | /* Write 'BSA, BSC' of DataRAM */ | ||
2670 | value = onenand_buffer_address(dataram, sectors, count); | ||
2671 | this->write_word(value, this->base + ONENAND_REG_START_BUFFER); | ||
2672 | } | ||
2673 | |||
2674 | /* Interrupt clear */ | ||
2675 | this->write_word(ONENAND_INT_CLEAR, this->base + ONENAND_REG_INTERRUPT); | ||
2676 | |||
2677 | /* Write command */ | ||
2678 | this->write_word(cmd, this->base + ONENAND_REG_COMMAND); | ||
2679 | |||
2680 | return 0; | ||
2681 | } | ||
2682 | |||
2683 | /** | ||
2684 | * onenand_otp_write_oob_nolock - [Internal] OneNAND write out-of-band, specific to OTP | ||
2685 | * @param mtd MTD device structure | ||
2686 | * @param to offset to write to | ||
2687 | * @param len number of bytes to write | ||
2688 | * @param retlen pointer to variable to store the number of written bytes | ||
2689 | * @param buf the data to write | ||
2690 | * | ||
2691 | * OneNAND write out-of-band only for OTP | ||
2692 | */ | ||
2693 | static int onenand_otp_write_oob_nolock(struct mtd_info *mtd, loff_t to, | ||
2694 | struct mtd_oob_ops *ops) | ||
2695 | { | ||
2696 | struct onenand_chip *this = mtd->priv; | ||
2697 | int column, ret = 0, oobsize; | ||
2698 | int written = 0; | ||
2699 | u_char *oobbuf; | ||
2700 | size_t len = ops->ooblen; | ||
2701 | const u_char *buf = ops->oobbuf; | ||
2702 | int block, value, status; | ||
2703 | |||
2704 | to += ops->ooboffs; | ||
2705 | |||
2706 | /* Initialize retlen, in case of early exit */ | ||
2707 | ops->oobretlen = 0; | ||
2708 | |||
2709 | oobsize = mtd->oobsize; | ||
2710 | |||
2711 | column = to & (mtd->oobsize - 1); | ||
2712 | |||
2713 | oobbuf = this->oob_buf; | ||
2714 | |||
2715 | /* Loop until all data write */ | ||
2716 | while (written < len) { | ||
2717 | int thislen = min_t(int, oobsize, len - written); | ||
2718 | |||
2719 | cond_resched(); | ||
2720 | |||
2721 | block = (int) (to >> this->erase_shift); | ||
2722 | /* | ||
2723 | * Write 'DFS, FBA' of Flash | ||
2724 | * Add: F100h DQ=DFS, FBA | ||
2725 | */ | ||
2726 | |||
2727 | value = onenand_block_address(this, block); | ||
2728 | this->write_word(value, this->base + | ||
2729 | ONENAND_REG_START_ADDRESS1); | ||
2730 | |||
2731 | /* | ||
2732 | * Select DataRAM for DDP | ||
2733 | * Add: F101h DQ=DBS | ||
2734 | */ | ||
2735 | |||
2736 | value = onenand_bufferram_address(this, block); | ||
2737 | this->write_word(value, this->base + | ||
2738 | ONENAND_REG_START_ADDRESS2); | ||
2739 | ONENAND_SET_NEXT_BUFFERRAM(this); | ||
2740 | |||
2741 | /* | ||
2742 | * Enter OTP access mode | ||
2743 | */ | ||
2744 | this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); | ||
2745 | this->wait(mtd, FL_OTPING); | ||
2746 | |||
2747 | /* We send data to spare ram with oobsize | ||
2748 | * to prevent byte access */ | ||
2749 | memcpy(oobbuf + column, buf, thislen); | ||
2750 | |||
2751 | /* | ||
2752 | * Write Data into DataRAM | ||
2753 | * Add: 8th Word | ||
2754 | * in sector0/spare/page0 | ||
2755 | * DQ=XXFCh | ||
2756 | */ | ||
2757 | this->write_bufferram(mtd, ONENAND_SPARERAM, | ||
2758 | oobbuf, 0, mtd->oobsize); | ||
2759 | |||
2760 | onenand_otp_command(mtd, ONENAND_CMD_PROGOOB, to, mtd->oobsize); | ||
2761 | onenand_update_bufferram(mtd, to, 0); | ||
2762 | if (ONENAND_IS_2PLANE(this)) { | ||
2763 | ONENAND_SET_BUFFERRAM1(this); | ||
2764 | onenand_update_bufferram(mtd, to + this->writesize, 0); | ||
2765 | } | ||
2766 | |||
2767 | ret = this->wait(mtd, FL_WRITING); | ||
2768 | if (ret) { | ||
2769 | printk(KERN_ERR "%s: write failed %d\n", __func__, ret); | ||
2770 | break; | ||
2771 | } | ||
2772 | |||
2773 | /* Exit OTP access mode */ | ||
2774 | this->command(mtd, ONENAND_CMD_RESET, 0, 0); | ||
2775 | this->wait(mtd, FL_RESETING); | ||
2776 | |||
2777 | status = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); | ||
2778 | status &= 0x60; | ||
2779 | |||
2780 | if (status == 0x60) { | ||
2781 | printk(KERN_DEBUG "\nBLOCK\tSTATUS\n"); | ||
2782 | printk(KERN_DEBUG "1st Block\tLOCKED\n"); | ||
2783 | printk(KERN_DEBUG "OTP Block\tLOCKED\n"); | ||
2784 | } else if (status == 0x20) { | ||
2785 | printk(KERN_DEBUG "\nBLOCK\tSTATUS\n"); | ||
2786 | printk(KERN_DEBUG "1st Block\tLOCKED\n"); | ||
2787 | printk(KERN_DEBUG "OTP Block\tUN-LOCKED\n"); | ||
2788 | } else if (status == 0x40) { | ||
2789 | printk(KERN_DEBUG "\nBLOCK\tSTATUS\n"); | ||
2790 | printk(KERN_DEBUG "1st Block\tUN-LOCKED\n"); | ||
2791 | printk(KERN_DEBUG "OTP Block\tLOCKED\n"); | ||
2792 | } else { | ||
2793 | printk(KERN_DEBUG "Reboot to check\n"); | ||
2794 | } | ||
2795 | |||
2796 | written += thislen; | ||
2797 | if (written == len) | ||
2798 | break; | ||
2799 | |||
2800 | to += mtd->writesize; | ||
2801 | buf += thislen; | ||
2802 | column = 0; | ||
2803 | } | ||
2804 | |||
2805 | ops->oobretlen = written; | ||
2806 | |||
2807 | return ret; | ||
2808 | } | ||
2809 | |||
2594 | /* Internal OTP operation */ | 2810 | /* Internal OTP operation */ |
2595 | typedef int (*otp_op_t)(struct mtd_info *mtd, loff_t form, size_t len, | 2811 | typedef int (*otp_op_t)(struct mtd_info *mtd, loff_t form, size_t len, |
2596 | size_t *retlen, u_char *buf); | 2812 | size_t *retlen, u_char *buf); |
@@ -2693,11 +2909,11 @@ static int do_otp_lock(struct mtd_info *mtd, loff_t from, size_t len, | |||
2693 | struct mtd_oob_ops ops; | 2909 | struct mtd_oob_ops ops; |
2694 | int ret; | 2910 | int ret; |
2695 | 2911 | ||
2696 | /* Enter OTP access mode */ | ||
2697 | this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); | ||
2698 | this->wait(mtd, FL_OTPING); | ||
2699 | |||
2700 | if (FLEXONENAND(this)) { | 2912 | if (FLEXONENAND(this)) { |
2913 | |||
2914 | /* Enter OTP access mode */ | ||
2915 | this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); | ||
2916 | this->wait(mtd, FL_OTPING); | ||
2701 | /* | 2917 | /* |
2702 | * For Flex-OneNAND, we write lock mark to 1st word of sector 4 of | 2918 | * For Flex-OneNAND, we write lock mark to 1st word of sector 4 of |
2703 | * main area of page 49. | 2919 | * main area of page 49. |
@@ -2708,19 +2924,19 @@ static int do_otp_lock(struct mtd_info *mtd, loff_t from, size_t len, | |||
2708 | ops.oobbuf = NULL; | 2924 | ops.oobbuf = NULL; |
2709 | ret = onenand_write_ops_nolock(mtd, mtd->writesize * 49, &ops); | 2925 | ret = onenand_write_ops_nolock(mtd, mtd->writesize * 49, &ops); |
2710 | *retlen = ops.retlen; | 2926 | *retlen = ops.retlen; |
2927 | |||
2928 | /* Exit OTP access mode */ | ||
2929 | this->command(mtd, ONENAND_CMD_RESET, 0, 0); | ||
2930 | this->wait(mtd, FL_RESETING); | ||
2711 | } else { | 2931 | } else { |
2712 | ops.mode = MTD_OOB_PLACE; | 2932 | ops.mode = MTD_OOB_PLACE; |
2713 | ops.ooblen = len; | 2933 | ops.ooblen = len; |
2714 | ops.oobbuf = buf; | 2934 | ops.oobbuf = buf; |
2715 | ops.ooboffs = 0; | 2935 | ops.ooboffs = 0; |
2716 | ret = onenand_write_oob_nolock(mtd, from, &ops); | 2936 | ret = onenand_otp_write_oob_nolock(mtd, from, &ops); |
2717 | *retlen = ops.oobretlen; | 2937 | *retlen = ops.oobretlen; |
2718 | } | 2938 | } |
2719 | 2939 | ||
2720 | /* Exit OTP access mode */ | ||
2721 | this->command(mtd, ONENAND_CMD_RESET, 0, 0); | ||
2722 | this->wait(mtd, FL_RESETING); | ||
2723 | |||
2724 | return ret; | 2940 | return ret; |
2725 | } | 2941 | } |
2726 | 2942 | ||
@@ -2751,16 +2967,21 @@ static int onenand_otp_walk(struct mtd_info *mtd, loff_t from, size_t len, | |||
2751 | if (density < ONENAND_DEVICE_DENSITY_512Mb) | 2967 | if (density < ONENAND_DEVICE_DENSITY_512Mb) |
2752 | otp_pages = 20; | 2968 | otp_pages = 20; |
2753 | else | 2969 | else |
2754 | otp_pages = 10; | 2970 | otp_pages = 50; |
2755 | 2971 | ||
2756 | if (mode == MTD_OTP_FACTORY) { | 2972 | if (mode == MTD_OTP_FACTORY) { |
2757 | from += mtd->writesize * otp_pages; | 2973 | from += mtd->writesize * otp_pages; |
2758 | otp_pages = 64 - otp_pages; | 2974 | otp_pages = ONENAND_PAGES_PER_BLOCK - otp_pages; |
2759 | } | 2975 | } |
2760 | 2976 | ||
2761 | /* Check User/Factory boundary */ | 2977 | /* Check User/Factory boundary */ |
2762 | if (((mtd->writesize * otp_pages) - (from + len)) < 0) | 2978 | if (mode == MTD_OTP_USER) { |
2763 | return 0; | 2979 | if (((mtd->writesize * otp_pages) - (from + len)) < 0) |
2980 | return 0; | ||
2981 | } else { | ||
2982 | if (((mtd->writesize * otp_pages) - len) < 0) | ||
2983 | return 0; | ||
2984 | } | ||
2764 | 2985 | ||
2765 | onenand_get_device(mtd, FL_OTPING); | 2986 | onenand_get_device(mtd, FL_OTPING); |
2766 | while (len > 0 && otp_pages > 0) { | 2987 | while (len > 0 && otp_pages > 0) { |
@@ -2783,13 +3004,12 @@ static int onenand_otp_walk(struct mtd_info *mtd, loff_t from, size_t len, | |||
2783 | *retlen += sizeof(struct otp_info); | 3004 | *retlen += sizeof(struct otp_info); |
2784 | } else { | 3005 | } else { |
2785 | size_t tmp_retlen; | 3006 | size_t tmp_retlen; |
2786 | int size = len; | ||
2787 | 3007 | ||
2788 | ret = action(mtd, from, len, &tmp_retlen, buf); | 3008 | ret = action(mtd, from, len, &tmp_retlen, buf); |
2789 | 3009 | ||
2790 | buf += size; | 3010 | buf += tmp_retlen; |
2791 | len -= size; | 3011 | len -= tmp_retlen; |
2792 | *retlen += size; | 3012 | *retlen += tmp_retlen; |
2793 | 3013 | ||
2794 | if (ret) | 3014 | if (ret) |
2795 | break; | 3015 | break; |
@@ -2902,21 +3122,11 @@ static int onenand_lock_user_prot_reg(struct mtd_info *mtd, loff_t from, | |||
2902 | u_char *buf = FLEXONENAND(this) ? this->page_buf : this->oob_buf; | 3122 | u_char *buf = FLEXONENAND(this) ? this->page_buf : this->oob_buf; |
2903 | size_t retlen; | 3123 | size_t retlen; |
2904 | int ret; | 3124 | int ret; |
3125 | unsigned int otp_lock_offset = ONENAND_OTP_LOCK_OFFSET; | ||
2905 | 3126 | ||
2906 | memset(buf, 0xff, FLEXONENAND(this) ? this->writesize | 3127 | memset(buf, 0xff, FLEXONENAND(this) ? this->writesize |
2907 | : mtd->oobsize); | 3128 | : mtd->oobsize); |
2908 | /* | 3129 | /* |
2909 | * Note: OTP lock operation | ||
2910 | * OTP block : 0xXXFC | ||
2911 | * 1st block : 0xXXF3 (If chip support) | ||
2912 | * Both : 0xXXF0 (If chip support) | ||
2913 | */ | ||
2914 | if (FLEXONENAND(this)) | ||
2915 | buf[FLEXONENAND_OTP_LOCK_OFFSET] = 0xFC; | ||
2916 | else | ||
2917 | buf[ONENAND_OTP_LOCK_OFFSET] = 0xFC; | ||
2918 | |||
2919 | /* | ||
2920 | * Write lock mark to 8th word of sector0 of page0 of the spare0. | 3130 | * Write lock mark to 8th word of sector0 of page0 of the spare0. |
2921 | * We write 16 bytes spare area instead of 2 bytes. | 3131 | * We write 16 bytes spare area instead of 2 bytes. |
2922 | * For Flex-OneNAND, we write lock mark to 1st word of sector 4 of | 3132 | * For Flex-OneNAND, we write lock mark to 1st word of sector 4 of |
@@ -2926,10 +3136,30 @@ static int onenand_lock_user_prot_reg(struct mtd_info *mtd, loff_t from, | |||
2926 | from = 0; | 3136 | from = 0; |
2927 | len = FLEXONENAND(this) ? mtd->writesize : 16; | 3137 | len = FLEXONENAND(this) ? mtd->writesize : 16; |
2928 | 3138 | ||
3139 | /* | ||
3140 | * Note: OTP lock operation | ||
3141 | * OTP block : 0xXXFC XX 1111 1100 | ||
3142 | * 1st block : 0xXXF3 (If chip support) XX 1111 0011 | ||
3143 | * Both : 0xXXF0 (If chip support) XX 1111 0000 | ||
3144 | */ | ||
3145 | if (FLEXONENAND(this)) | ||
3146 | otp_lock_offset = FLEXONENAND_OTP_LOCK_OFFSET; | ||
3147 | |||
3148 | /* ONENAND_OTP_AREA | ONENAND_OTP_BLOCK0 | ONENAND_OTP_AREA_BLOCK0 */ | ||
3149 | if (otp == 1) | ||
3150 | buf[otp_lock_offset] = 0xFC; | ||
3151 | else if (otp == 2) | ||
3152 | buf[otp_lock_offset] = 0xF3; | ||
3153 | else if (otp == 3) | ||
3154 | buf[otp_lock_offset] = 0xF0; | ||
3155 | else if (otp != 0) | ||
3156 | printk(KERN_DEBUG "[OneNAND] Invalid option selected for OTP\n"); | ||
3157 | |||
2929 | ret = onenand_otp_walk(mtd, from, len, &retlen, buf, do_otp_lock, MTD_OTP_USER); | 3158 | ret = onenand_otp_walk(mtd, from, len, &retlen, buf, do_otp_lock, MTD_OTP_USER); |
2930 | 3159 | ||
2931 | return ret ? : retlen; | 3160 | return ret ? : retlen; |
2932 | } | 3161 | } |
3162 | |||
2933 | #endif /* CONFIG_MTD_ONENAND_OTP */ | 3163 | #endif /* CONFIG_MTD_ONENAND_OTP */ |
2934 | 3164 | ||
2935 | /** | 3165 | /** |