|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* |
| 3 | + * OTP support for SPI NOR flashes |
| 4 | + * |
| 5 | + * Copyright (C) 2021 Michael Walle <michael@walle.cc> |
| 6 | + */ |
| 7 | + |
| 8 | +#include <linux/log2.h> |
| 9 | +#include <linux/mtd/mtd.h> |
| 10 | +#include <linux/mtd/spi-nor.h> |
| 11 | + |
| 12 | +#include "core.h" |
| 13 | + |
| 14 | +#define spi_nor_otp_region_len(nor) ((nor)->params->otp.org->len) |
| 15 | +#define spi_nor_otp_n_regions(nor) ((nor)->params->otp.org->n_regions) |
| 16 | + |
| 17 | +static loff_t spi_nor_otp_region_start(const struct spi_nor *nor, unsigned int region) |
| 18 | +{ |
| 19 | + const struct spi_nor_otp_organization *org = nor->params->otp.org; |
| 20 | + |
| 21 | + return org->base + region * org->offset; |
| 22 | +} |
| 23 | + |
| 24 | +static size_t spi_nor_otp_size(struct spi_nor *nor) |
| 25 | +{ |
| 26 | + return spi_nor_otp_n_regions(nor) * spi_nor_otp_region_len(nor); |
| 27 | +} |
| 28 | + |
| 29 | +/* Translate the file offsets from and to OTP regions. */ |
| 30 | +static loff_t spi_nor_otp_region_to_offset(struct spi_nor *nor, unsigned int region) |
| 31 | +{ |
| 32 | + return region * spi_nor_otp_region_len(nor); |
| 33 | +} |
| 34 | + |
| 35 | +static unsigned int spi_nor_otp_offset_to_region(struct spi_nor *nor, loff_t ofs) |
| 36 | +{ |
| 37 | + return div64_u64(ofs, spi_nor_otp_region_len(nor)); |
| 38 | +} |
| 39 | + |
| 40 | +static int spi_nor_mtd_otp_info(struct mtd_info *mtd, size_t len, |
| 41 | + size_t *retlen, struct otp_info *buf) |
| 42 | +{ |
| 43 | + struct spi_nor *nor = mtd_to_spi_nor(mtd); |
| 44 | + const struct spi_nor_otp_ops *ops = nor->params->otp.ops; |
| 45 | + unsigned int n_regions = spi_nor_otp_n_regions(nor); |
| 46 | + unsigned int i; |
| 47 | + int ret, locked; |
| 48 | + |
| 49 | + if (len < n_regions * sizeof(*buf)) |
| 50 | + return -ENOSPC; |
| 51 | + |
| 52 | + ret = spi_nor_lock_and_prep(nor); |
| 53 | + if (ret) |
| 54 | + return ret; |
| 55 | + |
| 56 | + for (i = 0; i < n_regions; i++) { |
| 57 | + buf->start = spi_nor_otp_region_to_offset(nor, i); |
| 58 | + buf->length = spi_nor_otp_region_len(nor); |
| 59 | + |
| 60 | + locked = ops->is_locked(nor, i); |
| 61 | + if (locked < 0) { |
| 62 | + ret = locked; |
| 63 | + goto out; |
| 64 | + } |
| 65 | + |
| 66 | + buf->locked = !!locked; |
| 67 | + buf++; |
| 68 | + } |
| 69 | + |
| 70 | + *retlen = n_regions * sizeof(*buf); |
| 71 | + |
| 72 | +out: |
| 73 | + spi_nor_unlock_and_unprep(nor); |
| 74 | + |
| 75 | + return ret; |
| 76 | +} |
| 77 | + |
| 78 | +static int spi_nor_mtd_otp_read_write(struct mtd_info *mtd, loff_t ofs, |
| 79 | + size_t total_len, size_t *retlen, |
| 80 | + u8 *buf, bool is_write) |
| 81 | +{ |
| 82 | + struct spi_nor *nor = mtd_to_spi_nor(mtd); |
| 83 | + const struct spi_nor_otp_ops *ops = nor->params->otp.ops; |
| 84 | + const size_t rlen = spi_nor_otp_region_len(nor); |
| 85 | + loff_t rstart, rofs; |
| 86 | + unsigned int region; |
| 87 | + size_t len; |
| 88 | + int ret; |
| 89 | + |
| 90 | + if (ofs < 0 || ofs >= spi_nor_otp_size(nor)) |
| 91 | + return 0; |
| 92 | + |
| 93 | + ret = spi_nor_lock_and_prep(nor); |
| 94 | + if (ret) |
| 95 | + return ret; |
| 96 | + |
| 97 | + /* don't access beyond the end */ |
| 98 | + total_len = min_t(size_t, total_len, spi_nor_otp_size(nor) - ofs); |
| 99 | + |
| 100 | + *retlen = 0; |
| 101 | + while (total_len) { |
| 102 | + /* |
| 103 | + * The OTP regions are mapped into a contiguous area starting |
| 104 | + * at 0 as expected by the MTD layer. This will map the MTD |
| 105 | + * file offsets to the address of an OTP region as used in the |
| 106 | + * actual SPI commands. |
| 107 | + */ |
| 108 | + region = spi_nor_otp_offset_to_region(nor, ofs); |
| 109 | + rstart = spi_nor_otp_region_start(nor, region); |
| 110 | + |
| 111 | + /* |
| 112 | + * The size of a OTP region is expected to be a power of two, |
| 113 | + * thus we can just mask the lower bits and get the offset into |
| 114 | + * a region. |
| 115 | + */ |
| 116 | + rofs = ofs & (rlen - 1); |
| 117 | + |
| 118 | + /* don't access beyond one OTP region */ |
| 119 | + len = min_t(size_t, total_len, rlen - rofs); |
| 120 | + |
| 121 | + if (is_write) |
| 122 | + ret = ops->write(nor, rstart + rofs, len, buf); |
| 123 | + else |
| 124 | + ret = ops->read(nor, rstart + rofs, len, buf); |
| 125 | + if (ret == 0) |
| 126 | + ret = -EIO; |
| 127 | + if (ret < 0) |
| 128 | + goto out; |
| 129 | + |
| 130 | + *retlen += ret; |
| 131 | + ofs += ret; |
| 132 | + buf += ret; |
| 133 | + total_len -= ret; |
| 134 | + } |
| 135 | + ret = 0; |
| 136 | + |
| 137 | +out: |
| 138 | + spi_nor_unlock_and_unprep(nor); |
| 139 | + return ret; |
| 140 | +} |
| 141 | + |
| 142 | +static int spi_nor_mtd_otp_read(struct mtd_info *mtd, loff_t from, size_t len, |
| 143 | + size_t *retlen, u8 *buf) |
| 144 | +{ |
| 145 | + return spi_nor_mtd_otp_read_write(mtd, from, len, retlen, buf, false); |
| 146 | +} |
| 147 | + |
| 148 | +static int spi_nor_mtd_otp_write(struct mtd_info *mtd, loff_t to, size_t len, |
| 149 | + size_t *retlen, u8 *buf) |
| 150 | +{ |
| 151 | + return spi_nor_mtd_otp_read_write(mtd, to, len, retlen, buf, true); |
| 152 | +} |
| 153 | + |
| 154 | +static int spi_nor_mtd_otp_lock(struct mtd_info *mtd, loff_t from, size_t len) |
| 155 | +{ |
| 156 | + struct spi_nor *nor = mtd_to_spi_nor(mtd); |
| 157 | + const struct spi_nor_otp_ops *ops = nor->params->otp.ops; |
| 158 | + const size_t rlen = spi_nor_otp_region_len(nor); |
| 159 | + unsigned int region; |
| 160 | + int ret; |
| 161 | + |
| 162 | + if (from < 0 || (from + len) > spi_nor_otp_size(nor)) |
| 163 | + return -EINVAL; |
| 164 | + |
| 165 | + /* the user has to explicitly ask for whole regions */ |
| 166 | + if (!IS_ALIGNED(len, rlen) || !IS_ALIGNED(from, rlen)) |
| 167 | + return -EINVAL; |
| 168 | + |
| 169 | + ret = spi_nor_lock_and_prep(nor); |
| 170 | + if (ret) |
| 171 | + return ret; |
| 172 | + |
| 173 | + while (len) { |
| 174 | + region = spi_nor_otp_offset_to_region(nor, from); |
| 175 | + ret = ops->lock(nor, region); |
| 176 | + if (ret) |
| 177 | + goto out; |
| 178 | + |
| 179 | + len -= rlen; |
| 180 | + from += rlen; |
| 181 | + } |
| 182 | + |
| 183 | +out: |
| 184 | + spi_nor_unlock_and_unprep(nor); |
| 185 | + |
| 186 | + return ret; |
| 187 | +} |
| 188 | + |
| 189 | +void spi_nor_otp_init(struct spi_nor *nor) |
| 190 | +{ |
| 191 | + struct mtd_info *mtd = &nor->mtd; |
| 192 | + |
| 193 | + if (!nor->params->otp.ops) |
| 194 | + return; |
| 195 | + |
| 196 | + if (WARN_ON(!is_power_of_2(spi_nor_otp_region_len(nor)))) |
| 197 | + return; |
| 198 | + |
| 199 | + /* |
| 200 | + * We only support user_prot callbacks (yet). |
| 201 | + * |
| 202 | + * Some SPI NOR flashes like Macronix ones can be ordered in two |
| 203 | + * different variants. One with a factory locked OTP area and one where |
| 204 | + * it is left to the user to write to it. The factory locked OTP is |
| 205 | + * usually preprogrammed with an "electrical serial number". We don't |
| 206 | + * support these for now. |
| 207 | + */ |
| 208 | + mtd->_get_user_prot_info = spi_nor_mtd_otp_info; |
| 209 | + mtd->_read_user_prot_reg = spi_nor_mtd_otp_read; |
| 210 | + mtd->_write_user_prot_reg = spi_nor_mtd_otp_write; |
| 211 | + mtd->_lock_user_prot_reg = spi_nor_mtd_otp_lock; |
| 212 | +} |
0 commit comments