162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * NVM helpers
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2020, Intel Corporation
662306a36Sopenharmony_ci * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/idr.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include <linux/vmalloc.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "tb.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define NVM_MIN_SIZE		SZ_32K
1662306a36Sopenharmony_ci#define NVM_MAX_SIZE		SZ_1M
1762306a36Sopenharmony_ci#define NVM_DATA_DWORDS		16
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci/* Intel specific NVM offsets */
2062306a36Sopenharmony_ci#define INTEL_NVM_DEVID			0x05
2162306a36Sopenharmony_ci#define INTEL_NVM_VERSION		0x08
2262306a36Sopenharmony_ci#define INTEL_NVM_CSS			0x10
2362306a36Sopenharmony_ci#define INTEL_NVM_FLASH_SIZE		0x45
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci/* ASMedia specific NVM offsets */
2662306a36Sopenharmony_ci#define ASMEDIA_NVM_DATE		0x1c
2762306a36Sopenharmony_ci#define ASMEDIA_NVM_VERSION		0x28
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic DEFINE_IDA(nvm_ida);
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/**
3262306a36Sopenharmony_ci * struct tb_nvm_vendor_ops - Vendor specific NVM operations
3362306a36Sopenharmony_ci * @read_version: Reads out NVM version from the flash
3462306a36Sopenharmony_ci * @validate: Validates the NVM image before update (optional)
3562306a36Sopenharmony_ci * @write_headers: Writes headers before the rest of the image (optional)
3662306a36Sopenharmony_ci */
3762306a36Sopenharmony_cistruct tb_nvm_vendor_ops {
3862306a36Sopenharmony_ci	int (*read_version)(struct tb_nvm *nvm);
3962306a36Sopenharmony_ci	int (*validate)(struct tb_nvm *nvm);
4062306a36Sopenharmony_ci	int (*write_headers)(struct tb_nvm *nvm);
4162306a36Sopenharmony_ci};
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/**
4462306a36Sopenharmony_ci * struct tb_nvm_vendor - Vendor to &struct tb_nvm_vendor_ops mapping
4562306a36Sopenharmony_ci * @vendor: Vendor ID
4662306a36Sopenharmony_ci * @vops: Vendor specific NVM operations
4762306a36Sopenharmony_ci *
4862306a36Sopenharmony_ci * Maps vendor ID to NVM vendor operations. If there is no mapping then
4962306a36Sopenharmony_ci * NVM firmware upgrade is disabled for the device.
5062306a36Sopenharmony_ci */
5162306a36Sopenharmony_cistruct tb_nvm_vendor {
5262306a36Sopenharmony_ci	u16 vendor;
5362306a36Sopenharmony_ci	const struct tb_nvm_vendor_ops *vops;
5462306a36Sopenharmony_ci};
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic int intel_switch_nvm_version(struct tb_nvm *nvm)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(nvm->dev);
5962306a36Sopenharmony_ci	u32 val, nvm_size, hdr_size;
6062306a36Sopenharmony_ci	int ret;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	/*
6362306a36Sopenharmony_ci	 * If the switch is in safe-mode the only accessible portion of
6462306a36Sopenharmony_ci	 * the NVM is the non-active one where userspace is expected to
6562306a36Sopenharmony_ci	 * write new functional NVM.
6662306a36Sopenharmony_ci	 */
6762306a36Sopenharmony_ci	if (sw->safe_mode)
6862306a36Sopenharmony_ci		return 0;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	ret = tb_switch_nvm_read(sw, INTEL_NVM_FLASH_SIZE, &val, sizeof(val));
7162306a36Sopenharmony_ci	if (ret)
7262306a36Sopenharmony_ci		return ret;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	hdr_size = sw->generation < 3 ? SZ_8K : SZ_16K;
7562306a36Sopenharmony_ci	nvm_size = (SZ_1M << (val & 7)) / 8;
7662306a36Sopenharmony_ci	nvm_size = (nvm_size - hdr_size) / 2;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	ret = tb_switch_nvm_read(sw, INTEL_NVM_VERSION, &val, sizeof(val));
7962306a36Sopenharmony_ci	if (ret)
8062306a36Sopenharmony_ci		return ret;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	nvm->major = (val >> 16) & 0xff;
8362306a36Sopenharmony_ci	nvm->minor = (val >> 8) & 0xff;
8462306a36Sopenharmony_ci	nvm->active_size = nvm_size;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	return 0;
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic int intel_switch_nvm_validate(struct tb_nvm *nvm)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(nvm->dev);
9262306a36Sopenharmony_ci	unsigned int image_size, hdr_size;
9362306a36Sopenharmony_ci	u16 ds_size, device_id;
9462306a36Sopenharmony_ci	u8 *buf = nvm->buf;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	image_size = nvm->buf_data_size;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	/*
9962306a36Sopenharmony_ci	 * FARB pointer must point inside the image and must at least
10062306a36Sopenharmony_ci	 * contain parts of the digital section we will be reading here.
10162306a36Sopenharmony_ci	 */
10262306a36Sopenharmony_ci	hdr_size = (*(u32 *)buf) & 0xffffff;
10362306a36Sopenharmony_ci	if (hdr_size + INTEL_NVM_DEVID + 2 >= image_size)
10462306a36Sopenharmony_ci		return -EINVAL;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	/* Digital section start should be aligned to 4k page */
10762306a36Sopenharmony_ci	if (!IS_ALIGNED(hdr_size, SZ_4K))
10862306a36Sopenharmony_ci		return -EINVAL;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	/*
11162306a36Sopenharmony_ci	 * Read digital section size and check that it also fits inside
11262306a36Sopenharmony_ci	 * the image.
11362306a36Sopenharmony_ci	 */
11462306a36Sopenharmony_ci	ds_size = *(u16 *)(buf + hdr_size);
11562306a36Sopenharmony_ci	if (ds_size >= image_size)
11662306a36Sopenharmony_ci		return -EINVAL;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	if (sw->safe_mode)
11962306a36Sopenharmony_ci		return 0;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	/*
12262306a36Sopenharmony_ci	 * Make sure the device ID in the image matches the one
12362306a36Sopenharmony_ci	 * we read from the switch config space.
12462306a36Sopenharmony_ci	 */
12562306a36Sopenharmony_ci	device_id = *(u16 *)(buf + hdr_size + INTEL_NVM_DEVID);
12662306a36Sopenharmony_ci	if (device_id != sw->config.device_id)
12762306a36Sopenharmony_ci		return -EINVAL;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	/* Skip headers in the image */
13062306a36Sopenharmony_ci	nvm->buf_data_start = buf + hdr_size;
13162306a36Sopenharmony_ci	nvm->buf_data_size = image_size - hdr_size;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	return 0;
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic int intel_switch_nvm_write_headers(struct tb_nvm *nvm)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(nvm->dev);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (sw->generation < 3) {
14162306a36Sopenharmony_ci		int ret;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci		/* Write CSS headers first */
14462306a36Sopenharmony_ci		ret = dma_port_flash_write(sw->dma_port,
14562306a36Sopenharmony_ci			DMA_PORT_CSS_ADDRESS, nvm->buf + INTEL_NVM_CSS,
14662306a36Sopenharmony_ci			DMA_PORT_CSS_MAX_SIZE);
14762306a36Sopenharmony_ci		if (ret)
14862306a36Sopenharmony_ci			return ret;
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	return 0;
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic const struct tb_nvm_vendor_ops intel_switch_nvm_ops = {
15562306a36Sopenharmony_ci	.read_version = intel_switch_nvm_version,
15662306a36Sopenharmony_ci	.validate = intel_switch_nvm_validate,
15762306a36Sopenharmony_ci	.write_headers = intel_switch_nvm_write_headers,
15862306a36Sopenharmony_ci};
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic int asmedia_switch_nvm_version(struct tb_nvm *nvm)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(nvm->dev);
16362306a36Sopenharmony_ci	u32 val;
16462306a36Sopenharmony_ci	int ret;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	ret = tb_switch_nvm_read(sw, ASMEDIA_NVM_VERSION, &val, sizeof(val));
16762306a36Sopenharmony_ci	if (ret)
16862306a36Sopenharmony_ci		return ret;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	nvm->major = (val << 16) & 0xff0000;
17162306a36Sopenharmony_ci	nvm->major |= val & 0x00ff00;
17262306a36Sopenharmony_ci	nvm->major |= (val >> 16) & 0x0000ff;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	ret = tb_switch_nvm_read(sw, ASMEDIA_NVM_DATE, &val, sizeof(val));
17562306a36Sopenharmony_ci	if (ret)
17662306a36Sopenharmony_ci		return ret;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	nvm->minor = (val << 16) & 0xff0000;
17962306a36Sopenharmony_ci	nvm->minor |= val & 0x00ff00;
18062306a36Sopenharmony_ci	nvm->minor |= (val >> 16) & 0x0000ff;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	/* ASMedia NVM size is fixed to 512k */
18362306a36Sopenharmony_ci	nvm->active_size = SZ_512K;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	return 0;
18662306a36Sopenharmony_ci}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_cistatic const struct tb_nvm_vendor_ops asmedia_switch_nvm_ops = {
18962306a36Sopenharmony_ci	.read_version = asmedia_switch_nvm_version,
19062306a36Sopenharmony_ci};
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci/* Router vendor NVM support table */
19362306a36Sopenharmony_cistatic const struct tb_nvm_vendor switch_nvm_vendors[] = {
19462306a36Sopenharmony_ci	{ 0x174c, &asmedia_switch_nvm_ops },
19562306a36Sopenharmony_ci	{ PCI_VENDOR_ID_INTEL, &intel_switch_nvm_ops },
19662306a36Sopenharmony_ci	{ 0x8087, &intel_switch_nvm_ops },
19762306a36Sopenharmony_ci};
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistatic int intel_retimer_nvm_version(struct tb_nvm *nvm)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	struct tb_retimer *rt = tb_to_retimer(nvm->dev);
20262306a36Sopenharmony_ci	u32 val, nvm_size;
20362306a36Sopenharmony_ci	int ret;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	ret = tb_retimer_nvm_read(rt, INTEL_NVM_VERSION, &val, sizeof(val));
20662306a36Sopenharmony_ci	if (ret)
20762306a36Sopenharmony_ci		return ret;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	nvm->major = (val >> 16) & 0xff;
21062306a36Sopenharmony_ci	nvm->minor = (val >> 8) & 0xff;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	ret = tb_retimer_nvm_read(rt, INTEL_NVM_FLASH_SIZE, &val, sizeof(val));
21362306a36Sopenharmony_ci	if (ret)
21462306a36Sopenharmony_ci		return ret;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	nvm_size = (SZ_1M << (val & 7)) / 8;
21762306a36Sopenharmony_ci	nvm_size = (nvm_size - SZ_16K) / 2;
21862306a36Sopenharmony_ci	nvm->active_size = nvm_size;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	return 0;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_cistatic int intel_retimer_nvm_validate(struct tb_nvm *nvm)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	struct tb_retimer *rt = tb_to_retimer(nvm->dev);
22662306a36Sopenharmony_ci	unsigned int image_size, hdr_size;
22762306a36Sopenharmony_ci	u8 *buf = nvm->buf;
22862306a36Sopenharmony_ci	u16 ds_size, device;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	image_size = nvm->buf_data_size;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	/*
23362306a36Sopenharmony_ci	 * FARB pointer must point inside the image and must at least
23462306a36Sopenharmony_ci	 * contain parts of the digital section we will be reading here.
23562306a36Sopenharmony_ci	 */
23662306a36Sopenharmony_ci	hdr_size = (*(u32 *)buf) & 0xffffff;
23762306a36Sopenharmony_ci	if (hdr_size + INTEL_NVM_DEVID + 2 >= image_size)
23862306a36Sopenharmony_ci		return -EINVAL;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	/* Digital section start should be aligned to 4k page */
24162306a36Sopenharmony_ci	if (!IS_ALIGNED(hdr_size, SZ_4K))
24262306a36Sopenharmony_ci		return -EINVAL;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	/*
24562306a36Sopenharmony_ci	 * Read digital section size and check that it also fits inside
24662306a36Sopenharmony_ci	 * the image.
24762306a36Sopenharmony_ci	 */
24862306a36Sopenharmony_ci	ds_size = *(u16 *)(buf + hdr_size);
24962306a36Sopenharmony_ci	if (ds_size >= image_size)
25062306a36Sopenharmony_ci		return -EINVAL;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	/*
25362306a36Sopenharmony_ci	 * Make sure the device ID in the image matches the retimer
25462306a36Sopenharmony_ci	 * hardware.
25562306a36Sopenharmony_ci	 */
25662306a36Sopenharmony_ci	device = *(u16 *)(buf + hdr_size + INTEL_NVM_DEVID);
25762306a36Sopenharmony_ci	if (device != rt->device)
25862306a36Sopenharmony_ci		return -EINVAL;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	/* Skip headers in the image */
26162306a36Sopenharmony_ci	nvm->buf_data_start = buf + hdr_size;
26262306a36Sopenharmony_ci	nvm->buf_data_size = image_size - hdr_size;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	return 0;
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic const struct tb_nvm_vendor_ops intel_retimer_nvm_ops = {
26862306a36Sopenharmony_ci	.read_version = intel_retimer_nvm_version,
26962306a36Sopenharmony_ci	.validate = intel_retimer_nvm_validate,
27062306a36Sopenharmony_ci};
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci/* Retimer vendor NVM support table */
27362306a36Sopenharmony_cistatic const struct tb_nvm_vendor retimer_nvm_vendors[] = {
27462306a36Sopenharmony_ci	{ 0x8087, &intel_retimer_nvm_ops },
27562306a36Sopenharmony_ci};
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci/**
27862306a36Sopenharmony_ci * tb_nvm_alloc() - Allocate new NVM structure
27962306a36Sopenharmony_ci * @dev: Device owning the NVM
28062306a36Sopenharmony_ci *
28162306a36Sopenharmony_ci * Allocates new NVM structure with unique @id and returns it. In case
28262306a36Sopenharmony_ci * of error returns ERR_PTR(). Specifically returns %-EOPNOTSUPP if the
28362306a36Sopenharmony_ci * NVM format of the @dev is not known by the kernel.
28462306a36Sopenharmony_ci */
28562306a36Sopenharmony_cistruct tb_nvm *tb_nvm_alloc(struct device *dev)
28662306a36Sopenharmony_ci{
28762306a36Sopenharmony_ci	const struct tb_nvm_vendor_ops *vops = NULL;
28862306a36Sopenharmony_ci	struct tb_nvm *nvm;
28962306a36Sopenharmony_ci	int ret, i;
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	if (tb_is_switch(dev)) {
29262306a36Sopenharmony_ci		const struct tb_switch *sw = tb_to_switch(dev);
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(switch_nvm_vendors); i++) {
29562306a36Sopenharmony_ci			const struct tb_nvm_vendor *v = &switch_nvm_vendors[i];
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci			if (v->vendor == sw->config.vendor_id) {
29862306a36Sopenharmony_ci				vops = v->vops;
29962306a36Sopenharmony_ci				break;
30062306a36Sopenharmony_ci			}
30162306a36Sopenharmony_ci		}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci		if (!vops) {
30462306a36Sopenharmony_ci			tb_sw_dbg(sw, "router NVM format of vendor %#x unknown\n",
30562306a36Sopenharmony_ci				  sw->config.vendor_id);
30662306a36Sopenharmony_ci			return ERR_PTR(-EOPNOTSUPP);
30762306a36Sopenharmony_ci		}
30862306a36Sopenharmony_ci	} else if (tb_is_retimer(dev)) {
30962306a36Sopenharmony_ci		const struct tb_retimer *rt = tb_to_retimer(dev);
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(retimer_nvm_vendors); i++) {
31262306a36Sopenharmony_ci			const struct tb_nvm_vendor *v = &retimer_nvm_vendors[i];
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci			if (v->vendor == rt->vendor) {
31562306a36Sopenharmony_ci				vops = v->vops;
31662306a36Sopenharmony_ci				break;
31762306a36Sopenharmony_ci			}
31862306a36Sopenharmony_ci		}
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci		if (!vops) {
32162306a36Sopenharmony_ci			dev_dbg(dev, "retimer NVM format of vendor %#x unknown\n",
32262306a36Sopenharmony_ci				rt->vendor);
32362306a36Sopenharmony_ci			return ERR_PTR(-EOPNOTSUPP);
32462306a36Sopenharmony_ci		}
32562306a36Sopenharmony_ci	} else {
32662306a36Sopenharmony_ci		return ERR_PTR(-EOPNOTSUPP);
32762306a36Sopenharmony_ci	}
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	nvm = kzalloc(sizeof(*nvm), GFP_KERNEL);
33062306a36Sopenharmony_ci	if (!nvm)
33162306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	ret = ida_simple_get(&nvm_ida, 0, 0, GFP_KERNEL);
33462306a36Sopenharmony_ci	if (ret < 0) {
33562306a36Sopenharmony_ci		kfree(nvm);
33662306a36Sopenharmony_ci		return ERR_PTR(ret);
33762306a36Sopenharmony_ci	}
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	nvm->id = ret;
34062306a36Sopenharmony_ci	nvm->dev = dev;
34162306a36Sopenharmony_ci	nvm->vops = vops;
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	return nvm;
34462306a36Sopenharmony_ci}
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci/**
34762306a36Sopenharmony_ci * tb_nvm_read_version() - Read and populate NVM version
34862306a36Sopenharmony_ci * @nvm: NVM structure
34962306a36Sopenharmony_ci *
35062306a36Sopenharmony_ci * Uses vendor specific means to read out and fill in the existing
35162306a36Sopenharmony_ci * active NVM version. Returns %0 in case of success and negative errno
35262306a36Sopenharmony_ci * otherwise.
35362306a36Sopenharmony_ci */
35462306a36Sopenharmony_ciint tb_nvm_read_version(struct tb_nvm *nvm)
35562306a36Sopenharmony_ci{
35662306a36Sopenharmony_ci	const struct tb_nvm_vendor_ops *vops = nvm->vops;
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	if (vops && vops->read_version)
35962306a36Sopenharmony_ci		return vops->read_version(nvm);
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	return -EOPNOTSUPP;
36262306a36Sopenharmony_ci}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci/**
36562306a36Sopenharmony_ci * tb_nvm_validate() - Validate new NVM image
36662306a36Sopenharmony_ci * @nvm: NVM structure
36762306a36Sopenharmony_ci *
36862306a36Sopenharmony_ci * Runs vendor specific validation over the new NVM image and if all
36962306a36Sopenharmony_ci * checks pass returns %0. As side effect updates @nvm->buf_data_start
37062306a36Sopenharmony_ci * and @nvm->buf_data_size fields to match the actual data to be written
37162306a36Sopenharmony_ci * to the NVM.
37262306a36Sopenharmony_ci *
37362306a36Sopenharmony_ci * If the validation does not pass then returns negative errno.
37462306a36Sopenharmony_ci */
37562306a36Sopenharmony_ciint tb_nvm_validate(struct tb_nvm *nvm)
37662306a36Sopenharmony_ci{
37762306a36Sopenharmony_ci	const struct tb_nvm_vendor_ops *vops = nvm->vops;
37862306a36Sopenharmony_ci	unsigned int image_size;
37962306a36Sopenharmony_ci	u8 *buf = nvm->buf;
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	if (!buf)
38262306a36Sopenharmony_ci		return -EINVAL;
38362306a36Sopenharmony_ci	if (!vops)
38462306a36Sopenharmony_ci		return -EOPNOTSUPP;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	/* Just do basic image size checks */
38762306a36Sopenharmony_ci	image_size = nvm->buf_data_size;
38862306a36Sopenharmony_ci	if (image_size < NVM_MIN_SIZE || image_size > NVM_MAX_SIZE)
38962306a36Sopenharmony_ci		return -EINVAL;
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	/*
39262306a36Sopenharmony_ci	 * Set the default data start in the buffer. The validate method
39362306a36Sopenharmony_ci	 * below can change this if needed.
39462306a36Sopenharmony_ci	 */
39562306a36Sopenharmony_ci	nvm->buf_data_start = buf;
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	return vops->validate ? vops->validate(nvm) : 0;
39862306a36Sopenharmony_ci}
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci/**
40162306a36Sopenharmony_ci * tb_nvm_write_headers() - Write headers before the rest of the image
40262306a36Sopenharmony_ci * @nvm: NVM structure
40362306a36Sopenharmony_ci *
40462306a36Sopenharmony_ci * If the vendor NVM format requires writing headers before the rest of
40562306a36Sopenharmony_ci * the image, this function does that. Can be called even if the device
40662306a36Sopenharmony_ci * does not need this.
40762306a36Sopenharmony_ci *
40862306a36Sopenharmony_ci * Returns %0 in case of success and negative errno otherwise.
40962306a36Sopenharmony_ci */
41062306a36Sopenharmony_ciint tb_nvm_write_headers(struct tb_nvm *nvm)
41162306a36Sopenharmony_ci{
41262306a36Sopenharmony_ci	const struct tb_nvm_vendor_ops *vops = nvm->vops;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	return vops->write_headers ? vops->write_headers(nvm) : 0;
41562306a36Sopenharmony_ci}
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci/**
41862306a36Sopenharmony_ci * tb_nvm_add_active() - Adds active NVMem device to NVM
41962306a36Sopenharmony_ci * @nvm: NVM structure
42062306a36Sopenharmony_ci * @reg_read: Pointer to the function to read the NVM (passed directly to the
42162306a36Sopenharmony_ci *	      NVMem device)
42262306a36Sopenharmony_ci *
42362306a36Sopenharmony_ci * Registers new active NVmem device for @nvm. The @reg_read is called
42462306a36Sopenharmony_ci * directly from NVMem so it must handle possible concurrent access if
42562306a36Sopenharmony_ci * needed. The first parameter passed to @reg_read is @nvm structure.
42662306a36Sopenharmony_ci * Returns %0 in success and negative errno otherwise.
42762306a36Sopenharmony_ci */
42862306a36Sopenharmony_ciint tb_nvm_add_active(struct tb_nvm *nvm, nvmem_reg_read_t reg_read)
42962306a36Sopenharmony_ci{
43062306a36Sopenharmony_ci	struct nvmem_config config;
43162306a36Sopenharmony_ci	struct nvmem_device *nvmem;
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	memset(&config, 0, sizeof(config));
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	config.name = "nvm_active";
43662306a36Sopenharmony_ci	config.reg_read = reg_read;
43762306a36Sopenharmony_ci	config.read_only = true;
43862306a36Sopenharmony_ci	config.id = nvm->id;
43962306a36Sopenharmony_ci	config.stride = 4;
44062306a36Sopenharmony_ci	config.word_size = 4;
44162306a36Sopenharmony_ci	config.size = nvm->active_size;
44262306a36Sopenharmony_ci	config.dev = nvm->dev;
44362306a36Sopenharmony_ci	config.owner = THIS_MODULE;
44462306a36Sopenharmony_ci	config.priv = nvm;
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	nvmem = nvmem_register(&config);
44762306a36Sopenharmony_ci	if (IS_ERR(nvmem))
44862306a36Sopenharmony_ci		return PTR_ERR(nvmem);
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	nvm->active = nvmem;
45162306a36Sopenharmony_ci	return 0;
45262306a36Sopenharmony_ci}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci/**
45562306a36Sopenharmony_ci * tb_nvm_write_buf() - Write data to @nvm buffer
45662306a36Sopenharmony_ci * @nvm: NVM structure
45762306a36Sopenharmony_ci * @offset: Offset where to write the data
45862306a36Sopenharmony_ci * @val: Data buffer to write
45962306a36Sopenharmony_ci * @bytes: Number of bytes to write
46062306a36Sopenharmony_ci *
46162306a36Sopenharmony_ci * Helper function to cache the new NVM image before it is actually
46262306a36Sopenharmony_ci * written to the flash. Copies @bytes from @val to @nvm->buf starting
46362306a36Sopenharmony_ci * from @offset.
46462306a36Sopenharmony_ci */
46562306a36Sopenharmony_ciint tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val,
46662306a36Sopenharmony_ci		     size_t bytes)
46762306a36Sopenharmony_ci{
46862306a36Sopenharmony_ci	if (!nvm->buf) {
46962306a36Sopenharmony_ci		nvm->buf = vmalloc(NVM_MAX_SIZE);
47062306a36Sopenharmony_ci		if (!nvm->buf)
47162306a36Sopenharmony_ci			return -ENOMEM;
47262306a36Sopenharmony_ci	}
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	nvm->flushed = false;
47562306a36Sopenharmony_ci	nvm->buf_data_size = offset + bytes;
47662306a36Sopenharmony_ci	memcpy(nvm->buf + offset, val, bytes);
47762306a36Sopenharmony_ci	return 0;
47862306a36Sopenharmony_ci}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci/**
48162306a36Sopenharmony_ci * tb_nvm_add_non_active() - Adds non-active NVMem device to NVM
48262306a36Sopenharmony_ci * @nvm: NVM structure
48362306a36Sopenharmony_ci * @reg_write: Pointer to the function to write the NVM (passed directly
48462306a36Sopenharmony_ci *	       to the NVMem device)
48562306a36Sopenharmony_ci *
48662306a36Sopenharmony_ci * Registers new non-active NVmem device for @nvm. The @reg_write is called
48762306a36Sopenharmony_ci * directly from NVMem so it must handle possible concurrent access if
48862306a36Sopenharmony_ci * needed. The first parameter passed to @reg_write is @nvm structure.
48962306a36Sopenharmony_ci * The size of the NVMem device is set to %NVM_MAX_SIZE.
49062306a36Sopenharmony_ci *
49162306a36Sopenharmony_ci * Returns %0 in success and negative errno otherwise.
49262306a36Sopenharmony_ci */
49362306a36Sopenharmony_ciint tb_nvm_add_non_active(struct tb_nvm *nvm, nvmem_reg_write_t reg_write)
49462306a36Sopenharmony_ci{
49562306a36Sopenharmony_ci	struct nvmem_config config;
49662306a36Sopenharmony_ci	struct nvmem_device *nvmem;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	memset(&config, 0, sizeof(config));
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	config.name = "nvm_non_active";
50162306a36Sopenharmony_ci	config.reg_write = reg_write;
50262306a36Sopenharmony_ci	config.root_only = true;
50362306a36Sopenharmony_ci	config.id = nvm->id;
50462306a36Sopenharmony_ci	config.stride = 4;
50562306a36Sopenharmony_ci	config.word_size = 4;
50662306a36Sopenharmony_ci	config.size = NVM_MAX_SIZE;
50762306a36Sopenharmony_ci	config.dev = nvm->dev;
50862306a36Sopenharmony_ci	config.owner = THIS_MODULE;
50962306a36Sopenharmony_ci	config.priv = nvm;
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci	nvmem = nvmem_register(&config);
51262306a36Sopenharmony_ci	if (IS_ERR(nvmem))
51362306a36Sopenharmony_ci		return PTR_ERR(nvmem);
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	nvm->non_active = nvmem;
51662306a36Sopenharmony_ci	return 0;
51762306a36Sopenharmony_ci}
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci/**
52062306a36Sopenharmony_ci * tb_nvm_free() - Release NVM and its resources
52162306a36Sopenharmony_ci * @nvm: NVM structure to release
52262306a36Sopenharmony_ci *
52362306a36Sopenharmony_ci * Releases NVM and the NVMem devices if they were registered.
52462306a36Sopenharmony_ci */
52562306a36Sopenharmony_civoid tb_nvm_free(struct tb_nvm *nvm)
52662306a36Sopenharmony_ci{
52762306a36Sopenharmony_ci	if (nvm) {
52862306a36Sopenharmony_ci		nvmem_unregister(nvm->non_active);
52962306a36Sopenharmony_ci		nvmem_unregister(nvm->active);
53062306a36Sopenharmony_ci		vfree(nvm->buf);
53162306a36Sopenharmony_ci		ida_simple_remove(&nvm_ida, nvm->id);
53262306a36Sopenharmony_ci	}
53362306a36Sopenharmony_ci	kfree(nvm);
53462306a36Sopenharmony_ci}
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci/**
53762306a36Sopenharmony_ci * tb_nvm_read_data() - Read data from NVM
53862306a36Sopenharmony_ci * @address: Start address on the flash
53962306a36Sopenharmony_ci * @buf: Buffer where the read data is copied
54062306a36Sopenharmony_ci * @size: Size of the buffer in bytes
54162306a36Sopenharmony_ci * @retries: Number of retries if block read fails
54262306a36Sopenharmony_ci * @read_block: Function that reads block from the flash
54362306a36Sopenharmony_ci * @read_block_data: Data passsed to @read_block
54462306a36Sopenharmony_ci *
54562306a36Sopenharmony_ci * This is a generic function that reads data from NVM or NVM like
54662306a36Sopenharmony_ci * device.
54762306a36Sopenharmony_ci *
54862306a36Sopenharmony_ci * Returns %0 on success and negative errno otherwise.
54962306a36Sopenharmony_ci */
55062306a36Sopenharmony_ciint tb_nvm_read_data(unsigned int address, void *buf, size_t size,
55162306a36Sopenharmony_ci		     unsigned int retries, read_block_fn read_block,
55262306a36Sopenharmony_ci		     void *read_block_data)
55362306a36Sopenharmony_ci{
55462306a36Sopenharmony_ci	do {
55562306a36Sopenharmony_ci		unsigned int dwaddress, dwords, offset;
55662306a36Sopenharmony_ci		u8 data[NVM_DATA_DWORDS * 4];
55762306a36Sopenharmony_ci		size_t nbytes;
55862306a36Sopenharmony_ci		int ret;
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_ci		offset = address & 3;
56162306a36Sopenharmony_ci		nbytes = min_t(size_t, size + offset, NVM_DATA_DWORDS * 4);
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci		dwaddress = address / 4;
56462306a36Sopenharmony_ci		dwords = ALIGN(nbytes, 4) / 4;
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci		ret = read_block(read_block_data, dwaddress, data, dwords);
56762306a36Sopenharmony_ci		if (ret) {
56862306a36Sopenharmony_ci			if (ret != -ENODEV && retries--)
56962306a36Sopenharmony_ci				continue;
57062306a36Sopenharmony_ci			return ret;
57162306a36Sopenharmony_ci		}
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci		nbytes -= offset;
57462306a36Sopenharmony_ci		memcpy(buf, data + offset, nbytes);
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci		size -= nbytes;
57762306a36Sopenharmony_ci		address += nbytes;
57862306a36Sopenharmony_ci		buf += nbytes;
57962306a36Sopenharmony_ci	} while (size > 0);
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ci	return 0;
58262306a36Sopenharmony_ci}
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci/**
58562306a36Sopenharmony_ci * tb_nvm_write_data() - Write data to NVM
58662306a36Sopenharmony_ci * @address: Start address on the flash
58762306a36Sopenharmony_ci * @buf: Buffer where the data is copied from
58862306a36Sopenharmony_ci * @size: Size of the buffer in bytes
58962306a36Sopenharmony_ci * @retries: Number of retries if the block write fails
59062306a36Sopenharmony_ci * @write_block: Function that writes block to the flash
59162306a36Sopenharmony_ci * @write_block_data: Data passwd to @write_block
59262306a36Sopenharmony_ci *
59362306a36Sopenharmony_ci * This is generic function that writes data to NVM or NVM like device.
59462306a36Sopenharmony_ci *
59562306a36Sopenharmony_ci * Returns %0 on success and negative errno otherwise.
59662306a36Sopenharmony_ci */
59762306a36Sopenharmony_ciint tb_nvm_write_data(unsigned int address, const void *buf, size_t size,
59862306a36Sopenharmony_ci		      unsigned int retries, write_block_fn write_block,
59962306a36Sopenharmony_ci		      void *write_block_data)
60062306a36Sopenharmony_ci{
60162306a36Sopenharmony_ci	do {
60262306a36Sopenharmony_ci		unsigned int offset, dwaddress;
60362306a36Sopenharmony_ci		u8 data[NVM_DATA_DWORDS * 4];
60462306a36Sopenharmony_ci		size_t nbytes;
60562306a36Sopenharmony_ci		int ret;
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci		offset = address & 3;
60862306a36Sopenharmony_ci		nbytes = min_t(u32, size + offset, NVM_DATA_DWORDS * 4);
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci		memcpy(data + offset, buf, nbytes);
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci		dwaddress = address / 4;
61362306a36Sopenharmony_ci		ret = write_block(write_block_data, dwaddress, data, nbytes / 4);
61462306a36Sopenharmony_ci		if (ret) {
61562306a36Sopenharmony_ci			if (ret == -ETIMEDOUT) {
61662306a36Sopenharmony_ci				if (retries--)
61762306a36Sopenharmony_ci					continue;
61862306a36Sopenharmony_ci				ret = -EIO;
61962306a36Sopenharmony_ci			}
62062306a36Sopenharmony_ci			return ret;
62162306a36Sopenharmony_ci		}
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci		size -= nbytes;
62462306a36Sopenharmony_ci		address += nbytes;
62562306a36Sopenharmony_ci		buf += nbytes;
62662306a36Sopenharmony_ci	} while (size > 0);
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	return 0;
62962306a36Sopenharmony_ci}
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_civoid tb_nvm_exit(void)
63262306a36Sopenharmony_ci{
63362306a36Sopenharmony_ci	ida_destroy(&nvm_ida);
63462306a36Sopenharmony_ci}
635