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