162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci/* 462306a36Sopenharmony_ci * EEPROM driver for RAVE SP 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright (C) 2018 Zodiac Inflight Innovations 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/mfd/rave-sp.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/nvmem-provider.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/sizes.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci/** 1862306a36Sopenharmony_ci * enum rave_sp_eeprom_access_type - Supported types of EEPROM access 1962306a36Sopenharmony_ci * 2062306a36Sopenharmony_ci * @RAVE_SP_EEPROM_WRITE: EEPROM write 2162306a36Sopenharmony_ci * @RAVE_SP_EEPROM_READ: EEPROM read 2262306a36Sopenharmony_ci */ 2362306a36Sopenharmony_cienum rave_sp_eeprom_access_type { 2462306a36Sopenharmony_ci RAVE_SP_EEPROM_WRITE = 0, 2562306a36Sopenharmony_ci RAVE_SP_EEPROM_READ = 1, 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci/** 2962306a36Sopenharmony_ci * enum rave_sp_eeprom_header_size - EEPROM command header sizes 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci * @RAVE_SP_EEPROM_HEADER_SMALL: EEPROM header size for "small" devices (< 8K) 3262306a36Sopenharmony_ci * @RAVE_SP_EEPROM_HEADER_BIG: EEPROM header size for "big" devices (> 8K) 3362306a36Sopenharmony_ci */ 3462306a36Sopenharmony_cienum rave_sp_eeprom_header_size { 3562306a36Sopenharmony_ci RAVE_SP_EEPROM_HEADER_SMALL = 4U, 3662306a36Sopenharmony_ci RAVE_SP_EEPROM_HEADER_BIG = 5U, 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci#define RAVE_SP_EEPROM_HEADER_MAX RAVE_SP_EEPROM_HEADER_BIG 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#define RAVE_SP_EEPROM_PAGE_SIZE 32U 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci/** 4362306a36Sopenharmony_ci * struct rave_sp_eeprom_page - RAVE SP EEPROM page 4462306a36Sopenharmony_ci * 4562306a36Sopenharmony_ci * @type: Access type (see enum rave_sp_eeprom_access_type) 4662306a36Sopenharmony_ci * @success: Success flag (Success = 1, Failure = 0) 4762306a36Sopenharmony_ci * @data: Read data 4862306a36Sopenharmony_ci * 4962306a36Sopenharmony_ci * Note this structure corresponds to RSP_*_EEPROM payload from RAVE 5062306a36Sopenharmony_ci * SP ICD 5162306a36Sopenharmony_ci */ 5262306a36Sopenharmony_cistruct rave_sp_eeprom_page { 5362306a36Sopenharmony_ci u8 type; 5462306a36Sopenharmony_ci u8 success; 5562306a36Sopenharmony_ci u8 data[RAVE_SP_EEPROM_PAGE_SIZE]; 5662306a36Sopenharmony_ci} __packed; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci/** 5962306a36Sopenharmony_ci * struct rave_sp_eeprom - RAVE SP EEPROM device 6062306a36Sopenharmony_ci * 6162306a36Sopenharmony_ci * @sp: Pointer to parent RAVE SP device 6262306a36Sopenharmony_ci * @mutex: Lock protecting access to EEPROM 6362306a36Sopenharmony_ci * @address: EEPROM device address 6462306a36Sopenharmony_ci * @header_size: Size of EEPROM command header for this device 6562306a36Sopenharmony_ci * @dev: Pointer to corresponding struct device used for logging 6662306a36Sopenharmony_ci */ 6762306a36Sopenharmony_cistruct rave_sp_eeprom { 6862306a36Sopenharmony_ci struct rave_sp *sp; 6962306a36Sopenharmony_ci struct mutex mutex; 7062306a36Sopenharmony_ci u8 address; 7162306a36Sopenharmony_ci unsigned int header_size; 7262306a36Sopenharmony_ci struct device *dev; 7362306a36Sopenharmony_ci}; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci/** 7662306a36Sopenharmony_ci * rave_sp_eeprom_io - Low-level part of EEPROM page access 7762306a36Sopenharmony_ci * 7862306a36Sopenharmony_ci * @eeprom: EEPROM device to write to 7962306a36Sopenharmony_ci * @type: EEPROM access type (read or write) 8062306a36Sopenharmony_ci * @idx: number of the EEPROM page 8162306a36Sopenharmony_ci * @page: Data to write or buffer to store result (via page->data) 8262306a36Sopenharmony_ci * 8362306a36Sopenharmony_ci * This function does all of the low-level work required to perform a 8462306a36Sopenharmony_ci * EEPROM access. This includes formatting correct command payload, 8562306a36Sopenharmony_ci * sending it and checking received results. 8662306a36Sopenharmony_ci * 8762306a36Sopenharmony_ci * Returns zero in case of success or negative error code in 8862306a36Sopenharmony_ci * case of failure. 8962306a36Sopenharmony_ci */ 9062306a36Sopenharmony_cistatic int rave_sp_eeprom_io(struct rave_sp_eeprom *eeprom, 9162306a36Sopenharmony_ci enum rave_sp_eeprom_access_type type, 9262306a36Sopenharmony_ci u16 idx, 9362306a36Sopenharmony_ci struct rave_sp_eeprom_page *page) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci const bool is_write = type == RAVE_SP_EEPROM_WRITE; 9662306a36Sopenharmony_ci const unsigned int data_size = is_write ? sizeof(page->data) : 0; 9762306a36Sopenharmony_ci const unsigned int cmd_size = eeprom->header_size + data_size; 9862306a36Sopenharmony_ci const unsigned int rsp_size = 9962306a36Sopenharmony_ci is_write ? sizeof(*page) - sizeof(page->data) : sizeof(*page); 10062306a36Sopenharmony_ci unsigned int offset = 0; 10162306a36Sopenharmony_ci u8 cmd[RAVE_SP_EEPROM_HEADER_MAX + sizeof(page->data)]; 10262306a36Sopenharmony_ci int ret; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (WARN_ON(cmd_size > sizeof(cmd))) 10562306a36Sopenharmony_ci return -EINVAL; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci cmd[offset++] = eeprom->address; 10862306a36Sopenharmony_ci cmd[offset++] = 0; 10962306a36Sopenharmony_ci cmd[offset++] = type; 11062306a36Sopenharmony_ci cmd[offset++] = idx; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci /* 11362306a36Sopenharmony_ci * If there's still room in this command's header it means we 11462306a36Sopenharmony_ci * are talkin to EEPROM that uses 16-bit page numbers and we 11562306a36Sopenharmony_ci * have to specify index's MSB in payload as well. 11662306a36Sopenharmony_ci */ 11762306a36Sopenharmony_ci if (offset < eeprom->header_size) 11862306a36Sopenharmony_ci cmd[offset++] = idx >> 8; 11962306a36Sopenharmony_ci /* 12062306a36Sopenharmony_ci * Copy our data to write to command buffer first. In case of 12162306a36Sopenharmony_ci * a read data_size should be zero and memcpy would become a 12262306a36Sopenharmony_ci * no-op 12362306a36Sopenharmony_ci */ 12462306a36Sopenharmony_ci memcpy(&cmd[offset], page->data, data_size); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci ret = rave_sp_exec(eeprom->sp, cmd, cmd_size, page, rsp_size); 12762306a36Sopenharmony_ci if (ret) 12862306a36Sopenharmony_ci return ret; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci if (page->type != type) 13162306a36Sopenharmony_ci return -EPROTO; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (!page->success) 13462306a36Sopenharmony_ci return -EIO; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci return 0; 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci/** 14062306a36Sopenharmony_ci * rave_sp_eeprom_page_access - Access single EEPROM page 14162306a36Sopenharmony_ci * 14262306a36Sopenharmony_ci * @eeprom: EEPROM device to access 14362306a36Sopenharmony_ci * @type: Access type to perform (read or write) 14462306a36Sopenharmony_ci * @offset: Offset within EEPROM to access 14562306a36Sopenharmony_ci * @data: Data buffer 14662306a36Sopenharmony_ci * @data_len: Size of the data buffer 14762306a36Sopenharmony_ci * 14862306a36Sopenharmony_ci * This function performs a generic access to a single page or a 14962306a36Sopenharmony_ci * portion thereof. Requested access MUST NOT cross the EEPROM page 15062306a36Sopenharmony_ci * boundary. 15162306a36Sopenharmony_ci * 15262306a36Sopenharmony_ci * Returns zero in case of success or negative error code in 15362306a36Sopenharmony_ci * case of failure. 15462306a36Sopenharmony_ci */ 15562306a36Sopenharmony_cistatic int 15662306a36Sopenharmony_cirave_sp_eeprom_page_access(struct rave_sp_eeprom *eeprom, 15762306a36Sopenharmony_ci enum rave_sp_eeprom_access_type type, 15862306a36Sopenharmony_ci unsigned int offset, u8 *data, 15962306a36Sopenharmony_ci size_t data_len) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci const unsigned int page_offset = offset % RAVE_SP_EEPROM_PAGE_SIZE; 16262306a36Sopenharmony_ci const unsigned int page_nr = offset / RAVE_SP_EEPROM_PAGE_SIZE; 16362306a36Sopenharmony_ci struct rave_sp_eeprom_page page; 16462306a36Sopenharmony_ci int ret; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* 16762306a36Sopenharmony_ci * This function will not work if data access we've been asked 16862306a36Sopenharmony_ci * to do is crossing EEPROM page boundary. Normally this 16962306a36Sopenharmony_ci * should never happen and getting here would indicate a bug 17062306a36Sopenharmony_ci * in the code. 17162306a36Sopenharmony_ci */ 17262306a36Sopenharmony_ci if (WARN_ON(data_len > sizeof(page.data) - page_offset)) 17362306a36Sopenharmony_ci return -EINVAL; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci if (type == RAVE_SP_EEPROM_WRITE) { 17662306a36Sopenharmony_ci /* 17762306a36Sopenharmony_ci * If doing a partial write we need to do a read first 17862306a36Sopenharmony_ci * to fill the rest of the page with correct data. 17962306a36Sopenharmony_ci */ 18062306a36Sopenharmony_ci if (data_len < RAVE_SP_EEPROM_PAGE_SIZE) { 18162306a36Sopenharmony_ci ret = rave_sp_eeprom_io(eeprom, RAVE_SP_EEPROM_READ, 18262306a36Sopenharmony_ci page_nr, &page); 18362306a36Sopenharmony_ci if (ret) 18462306a36Sopenharmony_ci return ret; 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci memcpy(&page.data[page_offset], data, data_len); 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci ret = rave_sp_eeprom_io(eeprom, type, page_nr, &page); 19162306a36Sopenharmony_ci if (ret) 19262306a36Sopenharmony_ci return ret; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci /* 19562306a36Sopenharmony_ci * Since we receive the result of the read via 'page.data' 19662306a36Sopenharmony_ci * buffer we need to copy that to 'data' 19762306a36Sopenharmony_ci */ 19862306a36Sopenharmony_ci if (type == RAVE_SP_EEPROM_READ) 19962306a36Sopenharmony_ci memcpy(data, &page.data[page_offset], data_len); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci return 0; 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci/** 20562306a36Sopenharmony_ci * rave_sp_eeprom_access - Access EEPROM data 20662306a36Sopenharmony_ci * 20762306a36Sopenharmony_ci * @eeprom: EEPROM device to access 20862306a36Sopenharmony_ci * @type: Access type to perform (read or write) 20962306a36Sopenharmony_ci * @offset: Offset within EEPROM to access 21062306a36Sopenharmony_ci * @data: Data buffer 21162306a36Sopenharmony_ci * @data_len: Size of the data buffer 21262306a36Sopenharmony_ci * 21362306a36Sopenharmony_ci * This function performs a generic access (either read or write) at 21462306a36Sopenharmony_ci * arbitrary offset (not necessary page aligned) of arbitrary length 21562306a36Sopenharmony_ci * (is not constrained by EEPROM page size). 21662306a36Sopenharmony_ci * 21762306a36Sopenharmony_ci * Returns zero in case of success or negative error code in case of 21862306a36Sopenharmony_ci * failure. 21962306a36Sopenharmony_ci */ 22062306a36Sopenharmony_cistatic int rave_sp_eeprom_access(struct rave_sp_eeprom *eeprom, 22162306a36Sopenharmony_ci enum rave_sp_eeprom_access_type type, 22262306a36Sopenharmony_ci unsigned int offset, u8 *data, 22362306a36Sopenharmony_ci unsigned int data_len) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci unsigned int residue; 22662306a36Sopenharmony_ci unsigned int chunk; 22762306a36Sopenharmony_ci unsigned int head; 22862306a36Sopenharmony_ci int ret; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci mutex_lock(&eeprom->mutex); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci head = offset % RAVE_SP_EEPROM_PAGE_SIZE; 23362306a36Sopenharmony_ci residue = data_len; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci do { 23662306a36Sopenharmony_ci /* 23762306a36Sopenharmony_ci * First iteration, if we are doing an access that is 23862306a36Sopenharmony_ci * not 32-byte aligned, we need to access only data up 23962306a36Sopenharmony_ci * to a page boundary to avoid corssing it in 24062306a36Sopenharmony_ci * rave_sp_eeprom_page_access() 24162306a36Sopenharmony_ci */ 24262306a36Sopenharmony_ci if (unlikely(head)) { 24362306a36Sopenharmony_ci chunk = RAVE_SP_EEPROM_PAGE_SIZE - head; 24462306a36Sopenharmony_ci /* 24562306a36Sopenharmony_ci * This can only happen once per 24662306a36Sopenharmony_ci * rave_sp_eeprom_access() call, so we set 24762306a36Sopenharmony_ci * head to zero to process all the other 24862306a36Sopenharmony_ci * iterations normally. 24962306a36Sopenharmony_ci */ 25062306a36Sopenharmony_ci head = 0; 25162306a36Sopenharmony_ci } else { 25262306a36Sopenharmony_ci chunk = RAVE_SP_EEPROM_PAGE_SIZE; 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci /* 25662306a36Sopenharmony_ci * We should never read more that 'residue' bytes 25762306a36Sopenharmony_ci */ 25862306a36Sopenharmony_ci chunk = min(chunk, residue); 25962306a36Sopenharmony_ci ret = rave_sp_eeprom_page_access(eeprom, type, offset, 26062306a36Sopenharmony_ci data, chunk); 26162306a36Sopenharmony_ci if (ret) 26262306a36Sopenharmony_ci goto out; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci residue -= chunk; 26562306a36Sopenharmony_ci offset += chunk; 26662306a36Sopenharmony_ci data += chunk; 26762306a36Sopenharmony_ci } while (residue); 26862306a36Sopenharmony_ciout: 26962306a36Sopenharmony_ci mutex_unlock(&eeprom->mutex); 27062306a36Sopenharmony_ci return ret; 27162306a36Sopenharmony_ci} 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_cistatic int rave_sp_eeprom_reg_read(void *eeprom, unsigned int offset, 27462306a36Sopenharmony_ci void *val, size_t bytes) 27562306a36Sopenharmony_ci{ 27662306a36Sopenharmony_ci return rave_sp_eeprom_access(eeprom, RAVE_SP_EEPROM_READ, 27762306a36Sopenharmony_ci offset, val, bytes); 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_cistatic int rave_sp_eeprom_reg_write(void *eeprom, unsigned int offset, 28162306a36Sopenharmony_ci void *val, size_t bytes) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci return rave_sp_eeprom_access(eeprom, RAVE_SP_EEPROM_WRITE, 28462306a36Sopenharmony_ci offset, val, bytes); 28562306a36Sopenharmony_ci} 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_cistatic int rave_sp_eeprom_probe(struct platform_device *pdev) 28862306a36Sopenharmony_ci{ 28962306a36Sopenharmony_ci struct device *dev = &pdev->dev; 29062306a36Sopenharmony_ci struct rave_sp *sp = dev_get_drvdata(dev->parent); 29162306a36Sopenharmony_ci struct device_node *np = dev->of_node; 29262306a36Sopenharmony_ci struct nvmem_config config = { 0 }; 29362306a36Sopenharmony_ci struct rave_sp_eeprom *eeprom; 29462306a36Sopenharmony_ci struct nvmem_device *nvmem; 29562306a36Sopenharmony_ci u32 reg[2], size; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci if (of_property_read_u32_array(np, "reg", reg, ARRAY_SIZE(reg))) { 29862306a36Sopenharmony_ci dev_err(dev, "Failed to parse \"reg\" property\n"); 29962306a36Sopenharmony_ci return -EINVAL; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci size = reg[1]; 30362306a36Sopenharmony_ci /* 30462306a36Sopenharmony_ci * Per ICD, we have no more than 2 bytes to specify EEPROM 30562306a36Sopenharmony_ci * page. 30662306a36Sopenharmony_ci */ 30762306a36Sopenharmony_ci if (size > U16_MAX * RAVE_SP_EEPROM_PAGE_SIZE) { 30862306a36Sopenharmony_ci dev_err(dev, "Specified size is too big\n"); 30962306a36Sopenharmony_ci return -EINVAL; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci eeprom = devm_kzalloc(dev, sizeof(*eeprom), GFP_KERNEL); 31362306a36Sopenharmony_ci if (!eeprom) 31462306a36Sopenharmony_ci return -ENOMEM; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci eeprom->address = reg[0]; 31762306a36Sopenharmony_ci eeprom->sp = sp; 31862306a36Sopenharmony_ci eeprom->dev = dev; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci if (size > SZ_8K) 32162306a36Sopenharmony_ci eeprom->header_size = RAVE_SP_EEPROM_HEADER_BIG; 32262306a36Sopenharmony_ci else 32362306a36Sopenharmony_ci eeprom->header_size = RAVE_SP_EEPROM_HEADER_SMALL; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci mutex_init(&eeprom->mutex); 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci config.id = -1; 32862306a36Sopenharmony_ci of_property_read_string(np, "zii,eeprom-name", &config.name); 32962306a36Sopenharmony_ci config.priv = eeprom; 33062306a36Sopenharmony_ci config.dev = dev; 33162306a36Sopenharmony_ci config.size = size; 33262306a36Sopenharmony_ci config.reg_read = rave_sp_eeprom_reg_read; 33362306a36Sopenharmony_ci config.reg_write = rave_sp_eeprom_reg_write; 33462306a36Sopenharmony_ci config.word_size = 1; 33562306a36Sopenharmony_ci config.stride = 1; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci nvmem = devm_nvmem_register(dev, &config); 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(nvmem); 34062306a36Sopenharmony_ci} 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_cistatic const struct of_device_id rave_sp_eeprom_of_match[] = { 34362306a36Sopenharmony_ci { .compatible = "zii,rave-sp-eeprom" }, 34462306a36Sopenharmony_ci {} 34562306a36Sopenharmony_ci}; 34662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, rave_sp_eeprom_of_match); 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic struct platform_driver rave_sp_eeprom_driver = { 34962306a36Sopenharmony_ci .probe = rave_sp_eeprom_probe, 35062306a36Sopenharmony_ci .driver = { 35162306a36Sopenharmony_ci .name = KBUILD_MODNAME, 35262306a36Sopenharmony_ci .of_match_table = rave_sp_eeprom_of_match, 35362306a36Sopenharmony_ci }, 35462306a36Sopenharmony_ci}; 35562306a36Sopenharmony_cimodule_platform_driver(rave_sp_eeprom_driver); 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 35862306a36Sopenharmony_ciMODULE_AUTHOR("Andrey Vostrikov <andrey.vostrikov@cogentembedded.com>"); 35962306a36Sopenharmony_ciMODULE_AUTHOR("Nikita Yushchenko <nikita.yoush@cogentembedded.com>"); 36062306a36Sopenharmony_ciMODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); 36162306a36Sopenharmony_ciMODULE_DESCRIPTION("RAVE SP EEPROM driver"); 362