Mika Westerberg | 719a5fe | 2020-03-05 11:37:15 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * NVM helpers |
| 4 | * |
| 5 | * Copyright (C) 2020, Intel Corporation |
| 6 | * Author: Mika Westerberg <mika.westerberg@linux.intel.com> |
| 7 | */ |
| 8 | |
| 9 | #include <linux/idr.h> |
| 10 | #include <linux/slab.h> |
| 11 | #include <linux/vmalloc.h> |
| 12 | |
| 13 | #include "tb.h" |
| 14 | |
| 15 | static DEFINE_IDA(nvm_ida); |
| 16 | |
| 17 | /** |
| 18 | * tb_nvm_alloc() - Allocate new NVM structure |
| 19 | * @dev: Device owning the NVM |
| 20 | * |
| 21 | * Allocates new NVM structure with unique @id and returns it. In case |
| 22 | * of error returns ERR_PTR(). |
| 23 | */ |
| 24 | struct tb_nvm *tb_nvm_alloc(struct device *dev) |
| 25 | { |
| 26 | struct tb_nvm *nvm; |
| 27 | int ret; |
| 28 | |
| 29 | nvm = kzalloc(sizeof(*nvm), GFP_KERNEL); |
| 30 | if (!nvm) |
| 31 | return ERR_PTR(-ENOMEM); |
| 32 | |
| 33 | ret = ida_simple_get(&nvm_ida, 0, 0, GFP_KERNEL); |
| 34 | if (ret < 0) { |
| 35 | kfree(nvm); |
| 36 | return ERR_PTR(ret); |
| 37 | } |
| 38 | |
| 39 | nvm->id = ret; |
| 40 | nvm->dev = dev; |
| 41 | |
| 42 | return nvm; |
| 43 | } |
| 44 | |
| 45 | /** |
| 46 | * tb_nvm_add_active() - Adds active NVMem device to NVM |
| 47 | * @nvm: NVM structure |
| 48 | * @size: Size of the active NVM in bytes |
| 49 | * @reg_read: Pointer to the function to read the NVM (passed directly to the |
| 50 | * NVMem device) |
| 51 | * |
| 52 | * Registers new active NVmem device for @nvm. The @reg_read is called |
| 53 | * directly from NVMem so it must handle possible concurrent access if |
| 54 | * needed. The first parameter passed to @reg_read is @nvm structure. |
| 55 | * Returns %0 in success and negative errno otherwise. |
| 56 | */ |
| 57 | int tb_nvm_add_active(struct tb_nvm *nvm, size_t size, nvmem_reg_read_t reg_read) |
| 58 | { |
| 59 | struct nvmem_config config; |
| 60 | struct nvmem_device *nvmem; |
| 61 | |
| 62 | memset(&config, 0, sizeof(config)); |
| 63 | |
| 64 | config.name = "nvm_active"; |
| 65 | config.reg_read = reg_read; |
| 66 | config.read_only = true; |
| 67 | config.id = nvm->id; |
| 68 | config.stride = 4; |
| 69 | config.word_size = 4; |
| 70 | config.size = size; |
| 71 | config.dev = nvm->dev; |
| 72 | config.owner = THIS_MODULE; |
| 73 | config.priv = nvm; |
| 74 | |
| 75 | nvmem = nvmem_register(&config); |
| 76 | if (IS_ERR(nvmem)) |
| 77 | return PTR_ERR(nvmem); |
| 78 | |
| 79 | nvm->active = nvmem; |
| 80 | return 0; |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * tb_nvm_write_buf() - Write data to @nvm buffer |
| 85 | * @nvm: NVM structure |
| 86 | * @offset: Offset where to write the data |
| 87 | * @val: Data buffer to write |
| 88 | * @bytes: Number of bytes to write |
| 89 | * |
| 90 | * Helper function to cache the new NVM image before it is actually |
| 91 | * written to the flash. Copies @bytes from @val to @nvm->buf starting |
| 92 | * from @offset. |
| 93 | */ |
| 94 | int tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val, |
| 95 | size_t bytes) |
| 96 | { |
| 97 | if (!nvm->buf) { |
| 98 | nvm->buf = vmalloc(NVM_MAX_SIZE); |
| 99 | if (!nvm->buf) |
| 100 | return -ENOMEM; |
| 101 | } |
| 102 | |
Mario Limonciello | 4b794f8 | 2020-06-23 11:14:28 -0500 | [diff] [blame] | 103 | nvm->flushed = false; |
Mika Westerberg | 719a5fe | 2020-03-05 11:37:15 +0200 | [diff] [blame] | 104 | nvm->buf_data_size = offset + bytes; |
| 105 | memcpy(nvm->buf + offset, val, bytes); |
| 106 | return 0; |
| 107 | } |
| 108 | |
| 109 | /** |
| 110 | * tb_nvm_add_non_active() - Adds non-active NVMem device to NVM |
| 111 | * @nvm: NVM structure |
| 112 | * @size: Size of the non-active NVM in bytes |
| 113 | * @reg_write: Pointer to the function to write the NVM (passed directly |
| 114 | * to the NVMem device) |
| 115 | * |
| 116 | * Registers new non-active NVmem device for @nvm. The @reg_write is called |
| 117 | * directly from NVMem so it must handle possible concurrent access if |
| 118 | * needed. The first parameter passed to @reg_write is @nvm structure. |
| 119 | * Returns %0 in success and negative errno otherwise. |
| 120 | */ |
| 121 | int tb_nvm_add_non_active(struct tb_nvm *nvm, size_t size, |
| 122 | nvmem_reg_write_t reg_write) |
| 123 | { |
| 124 | struct nvmem_config config; |
| 125 | struct nvmem_device *nvmem; |
| 126 | |
| 127 | memset(&config, 0, sizeof(config)); |
| 128 | |
| 129 | config.name = "nvm_non_active"; |
| 130 | config.reg_write = reg_write; |
| 131 | config.root_only = true; |
| 132 | config.id = nvm->id; |
| 133 | config.stride = 4; |
| 134 | config.word_size = 4; |
| 135 | config.size = size; |
| 136 | config.dev = nvm->dev; |
| 137 | config.owner = THIS_MODULE; |
| 138 | config.priv = nvm; |
| 139 | |
| 140 | nvmem = nvmem_register(&config); |
| 141 | if (IS_ERR(nvmem)) |
| 142 | return PTR_ERR(nvmem); |
| 143 | |
| 144 | nvm->non_active = nvmem; |
| 145 | return 0; |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * tb_nvm_free() - Release NVM and its resources |
| 150 | * @nvm: NVM structure to release |
| 151 | * |
| 152 | * Releases NVM and the NVMem devices if they were registered. |
| 153 | */ |
| 154 | void tb_nvm_free(struct tb_nvm *nvm) |
| 155 | { |
| 156 | if (nvm) { |
| 157 | if (nvm->non_active) |
| 158 | nvmem_unregister(nvm->non_active); |
| 159 | if (nvm->active) |
| 160 | nvmem_unregister(nvm->active); |
| 161 | vfree(nvm->buf); |
| 162 | ida_simple_remove(&nvm_ida, nvm->id); |
| 163 | } |
| 164 | kfree(nvm); |
| 165 | } |
| 166 | |
Mika Westerberg | 9b38303 | 2021-04-01 16:54:15 +0300 | [diff] [blame] | 167 | /** |
| 168 | * tb_nvm_read_data() - Read data from NVM |
| 169 | * @address: Start address on the flash |
| 170 | * @buf: Buffer where the read data is copied |
| 171 | * @size: Size of the buffer in bytes |
| 172 | * @retries: Number of retries if block read fails |
| 173 | * @read_block: Function that reads block from the flash |
| 174 | * @read_block_data: Data passsed to @read_block |
| 175 | * |
| 176 | * This is a generic function that reads data from NVM or NVM like |
| 177 | * device. |
| 178 | * |
| 179 | * Returns %0 on success and negative errno otherwise. |
| 180 | */ |
| 181 | int tb_nvm_read_data(unsigned int address, void *buf, size_t size, |
| 182 | unsigned int retries, read_block_fn read_block, |
| 183 | void *read_block_data) |
| 184 | { |
| 185 | do { |
| 186 | unsigned int dwaddress, dwords, offset; |
| 187 | u8 data[NVM_DATA_DWORDS * 4]; |
| 188 | size_t nbytes; |
| 189 | int ret; |
| 190 | |
| 191 | offset = address & 3; |
| 192 | nbytes = min_t(size_t, size + offset, NVM_DATA_DWORDS * 4); |
| 193 | |
| 194 | dwaddress = address / 4; |
| 195 | dwords = ALIGN(nbytes, 4) / 4; |
| 196 | |
| 197 | ret = read_block(read_block_data, dwaddress, data, dwords); |
| 198 | if (ret) { |
| 199 | if (ret != -ENODEV && retries--) |
| 200 | continue; |
| 201 | return ret; |
| 202 | } |
| 203 | |
| 204 | nbytes -= offset; |
| 205 | memcpy(buf, data + offset, nbytes); |
| 206 | |
| 207 | size -= nbytes; |
| 208 | address += nbytes; |
| 209 | buf += nbytes; |
| 210 | } while (size > 0); |
| 211 | |
| 212 | return 0; |
| 213 | } |
| 214 | |
| 215 | /** |
| 216 | * tb_nvm_write_data() - Write data to NVM |
| 217 | * @address: Start address on the flash |
| 218 | * @buf: Buffer where the data is copied from |
| 219 | * @size: Size of the buffer in bytes |
| 220 | * @retries: Number of retries if the block write fails |
| 221 | * @write_block: Function that writes block to the flash |
| 222 | * @write_block_data: Data passwd to @write_block |
| 223 | * |
| 224 | * This is generic function that writes data to NVM or NVM like device. |
| 225 | * |
| 226 | * Returns %0 on success and negative errno otherwise. |
| 227 | */ |
| 228 | int tb_nvm_write_data(unsigned int address, const void *buf, size_t size, |
| 229 | unsigned int retries, write_block_fn write_block, |
| 230 | void *write_block_data) |
| 231 | { |
| 232 | do { |
| 233 | unsigned int offset, dwaddress; |
| 234 | u8 data[NVM_DATA_DWORDS * 4]; |
| 235 | size_t nbytes; |
| 236 | int ret; |
| 237 | |
| 238 | offset = address & 3; |
| 239 | nbytes = min_t(u32, size + offset, NVM_DATA_DWORDS * 4); |
| 240 | |
| 241 | memcpy(data + offset, buf, nbytes); |
| 242 | |
| 243 | dwaddress = address / 4; |
| 244 | ret = write_block(write_block_data, dwaddress, data, nbytes / 4); |
| 245 | if (ret) { |
| 246 | if (ret == -ETIMEDOUT) { |
| 247 | if (retries--) |
| 248 | continue; |
| 249 | ret = -EIO; |
| 250 | } |
| 251 | return ret; |
| 252 | } |
| 253 | |
| 254 | size -= nbytes; |
| 255 | address += nbytes; |
| 256 | buf += nbytes; |
| 257 | } while (size > 0); |
| 258 | |
| 259 | return 0; |
| 260 | } |
| 261 | |
Mika Westerberg | 719a5fe | 2020-03-05 11:37:15 +0200 | [diff] [blame] | 262 | void tb_nvm_exit(void) |
| 263 | { |
| 264 | ida_destroy(&nvm_ida); |
| 265 | } |