xref: /kernel/linux/linux-5.10/drivers/mtd/maps/pismo.c (revision 8c2ecf20)
18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * PISMO memory driver - http://www.pismoworld.org/
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * For ARM Realview and Versatile platforms
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci#include <linux/init.h>
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci#include <linux/i2c.h>
108c2ecf20Sopenharmony_ci#include <linux/slab.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
138c2ecf20Sopenharmony_ci#include <linux/mutex.h>
148c2ecf20Sopenharmony_ci#include <linux/mtd/physmap.h>
158c2ecf20Sopenharmony_ci#include <linux/mtd/plat-ram.h>
168c2ecf20Sopenharmony_ci#include <linux/mtd/pismo.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#define PISMO_NUM_CS	5
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_cistruct pismo_cs_block {
218c2ecf20Sopenharmony_ci	u8	type;
228c2ecf20Sopenharmony_ci	u8	width;
238c2ecf20Sopenharmony_ci	__le16	access;
248c2ecf20Sopenharmony_ci	__le32	size;
258c2ecf20Sopenharmony_ci	u32	reserved[2];
268c2ecf20Sopenharmony_ci	char	device[32];
278c2ecf20Sopenharmony_ci} __packed;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistruct pismo_eeprom {
308c2ecf20Sopenharmony_ci	struct pismo_cs_block cs[PISMO_NUM_CS];
318c2ecf20Sopenharmony_ci	char	board[15];
328c2ecf20Sopenharmony_ci	u8	sum;
338c2ecf20Sopenharmony_ci} __packed;
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistruct pismo_mem {
368c2ecf20Sopenharmony_ci	phys_addr_t base;
378c2ecf20Sopenharmony_ci	u32	size;
388c2ecf20Sopenharmony_ci	u16	access;
398c2ecf20Sopenharmony_ci	u8	width;
408c2ecf20Sopenharmony_ci	u8	type;
418c2ecf20Sopenharmony_ci};
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistruct pismo_data {
448c2ecf20Sopenharmony_ci	struct i2c_client	*client;
458c2ecf20Sopenharmony_ci	void			(*vpp)(void *, int);
468c2ecf20Sopenharmony_ci	void			*vpp_data;
478c2ecf20Sopenharmony_ci	struct platform_device	*dev[PISMO_NUM_CS];
488c2ecf20Sopenharmony_ci};
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic void pismo_set_vpp(struct platform_device *pdev, int on)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(pdev->dev.parent);
538c2ecf20Sopenharmony_ci	struct pismo_data *pismo = i2c_get_clientdata(client);
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	pismo->vpp(pismo->vpp_data, on);
568c2ecf20Sopenharmony_ci}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistatic unsigned int pismo_width_to_bytes(unsigned int width)
598c2ecf20Sopenharmony_ci{
608c2ecf20Sopenharmony_ci	width &= 15;
618c2ecf20Sopenharmony_ci	if (width > 2)
628c2ecf20Sopenharmony_ci		return 0;
638c2ecf20Sopenharmony_ci	return 1 << width;
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic int pismo_eeprom_read(struct i2c_client *client, void *buf, u8 addr,
678c2ecf20Sopenharmony_ci			     size_t size)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	int ret;
708c2ecf20Sopenharmony_ci	struct i2c_msg msg[] = {
718c2ecf20Sopenharmony_ci		{
728c2ecf20Sopenharmony_ci			.addr = client->addr,
738c2ecf20Sopenharmony_ci			.len = sizeof(addr),
748c2ecf20Sopenharmony_ci			.buf = &addr,
758c2ecf20Sopenharmony_ci		}, {
768c2ecf20Sopenharmony_ci			.addr = client->addr,
778c2ecf20Sopenharmony_ci			.flags = I2C_M_RD,
788c2ecf20Sopenharmony_ci			.len = size,
798c2ecf20Sopenharmony_ci			.buf = buf,
808c2ecf20Sopenharmony_ci		},
818c2ecf20Sopenharmony_ci	};
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	return ret == ARRAY_SIZE(msg) ? size : -EIO;
868c2ecf20Sopenharmony_ci}
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_cistatic int pismo_add_device(struct pismo_data *pismo, int i,
898c2ecf20Sopenharmony_ci			    struct pismo_mem *region, const char *name,
908c2ecf20Sopenharmony_ci			    void *pdata, size_t psize)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	struct platform_device *dev;
938c2ecf20Sopenharmony_ci	struct resource res = { };
948c2ecf20Sopenharmony_ci	phys_addr_t base = region->base;
958c2ecf20Sopenharmony_ci	int ret;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	if (base == ~0)
988c2ecf20Sopenharmony_ci		return -ENXIO;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	res.start = base;
1018c2ecf20Sopenharmony_ci	res.end = base + region->size - 1;
1028c2ecf20Sopenharmony_ci	res.flags = IORESOURCE_MEM;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	dev = platform_device_alloc(name, i);
1058c2ecf20Sopenharmony_ci	if (!dev)
1068c2ecf20Sopenharmony_ci		return -ENOMEM;
1078c2ecf20Sopenharmony_ci	dev->dev.parent = &pismo->client->dev;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	do {
1108c2ecf20Sopenharmony_ci		ret = platform_device_add_resources(dev, &res, 1);
1118c2ecf20Sopenharmony_ci		if (ret)
1128c2ecf20Sopenharmony_ci			break;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci		ret = platform_device_add_data(dev, pdata, psize);
1158c2ecf20Sopenharmony_ci		if (ret)
1168c2ecf20Sopenharmony_ci			break;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci		ret = platform_device_add(dev);
1198c2ecf20Sopenharmony_ci		if (ret)
1208c2ecf20Sopenharmony_ci			break;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci		pismo->dev[i] = dev;
1238c2ecf20Sopenharmony_ci		return 0;
1248c2ecf20Sopenharmony_ci	} while (0);
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	platform_device_put(dev);
1278c2ecf20Sopenharmony_ci	return ret;
1288c2ecf20Sopenharmony_ci}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_cistatic int pismo_add_nor(struct pismo_data *pismo, int i,
1318c2ecf20Sopenharmony_ci			 struct pismo_mem *region)
1328c2ecf20Sopenharmony_ci{
1338c2ecf20Sopenharmony_ci	struct physmap_flash_data data = {
1348c2ecf20Sopenharmony_ci		.width = region->width,
1358c2ecf20Sopenharmony_ci	};
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	if (pismo->vpp)
1388c2ecf20Sopenharmony_ci		data.set_vpp = pismo_set_vpp;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	return pismo_add_device(pismo, i, region, "physmap-flash",
1418c2ecf20Sopenharmony_ci		&data, sizeof(data));
1428c2ecf20Sopenharmony_ci}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_cistatic int pismo_add_sram(struct pismo_data *pismo, int i,
1458c2ecf20Sopenharmony_ci			  struct pismo_mem *region)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	struct platdata_mtd_ram data = {
1488c2ecf20Sopenharmony_ci		.bankwidth = region->width,
1498c2ecf20Sopenharmony_ci	};
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	return pismo_add_device(pismo, i, region, "mtd-ram",
1528c2ecf20Sopenharmony_ci		&data, sizeof(data));
1538c2ecf20Sopenharmony_ci}
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_cistatic void pismo_add_one(struct pismo_data *pismo, int i,
1568c2ecf20Sopenharmony_ci			  const struct pismo_cs_block *cs, phys_addr_t base)
1578c2ecf20Sopenharmony_ci{
1588c2ecf20Sopenharmony_ci	struct device *dev = &pismo->client->dev;
1598c2ecf20Sopenharmony_ci	struct pismo_mem region;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	region.base = base;
1628c2ecf20Sopenharmony_ci	region.type = cs->type;
1638c2ecf20Sopenharmony_ci	region.width = pismo_width_to_bytes(cs->width);
1648c2ecf20Sopenharmony_ci	region.access = le16_to_cpu(cs->access);
1658c2ecf20Sopenharmony_ci	region.size = le32_to_cpu(cs->size);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	if (region.width == 0) {
1688c2ecf20Sopenharmony_ci		dev_err(dev, "cs%u: bad width: %02x, ignoring\n", i, cs->width);
1698c2ecf20Sopenharmony_ci		return;
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	/*
1738c2ecf20Sopenharmony_ci	 * FIXME: may need to the platforms memory controller here, but at
1748c2ecf20Sopenharmony_ci	 * the moment we assume that it has already been correctly setup.
1758c2ecf20Sopenharmony_ci	 * The memory controller can also tell us the base address as well.
1768c2ecf20Sopenharmony_ci	 */
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	dev_info(dev, "cs%u: %.32s: type %02x access %u00ps size %uK\n",
1798c2ecf20Sopenharmony_ci		i, cs->device, region.type, region.access, region.size / 1024);
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	switch (region.type) {
1828c2ecf20Sopenharmony_ci	case 0:
1838c2ecf20Sopenharmony_ci		break;
1848c2ecf20Sopenharmony_ci	case 1:
1858c2ecf20Sopenharmony_ci		/* static DOC */
1868c2ecf20Sopenharmony_ci		break;
1878c2ecf20Sopenharmony_ci	case 2:
1888c2ecf20Sopenharmony_ci		/* static NOR */
1898c2ecf20Sopenharmony_ci		pismo_add_nor(pismo, i, &region);
1908c2ecf20Sopenharmony_ci		break;
1918c2ecf20Sopenharmony_ci	case 3:
1928c2ecf20Sopenharmony_ci		/* static RAM */
1938c2ecf20Sopenharmony_ci		pismo_add_sram(pismo, i, &region);
1948c2ecf20Sopenharmony_ci		break;
1958c2ecf20Sopenharmony_ci	}
1968c2ecf20Sopenharmony_ci}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_cistatic int pismo_remove(struct i2c_client *client)
1998c2ecf20Sopenharmony_ci{
2008c2ecf20Sopenharmony_ci	struct pismo_data *pismo = i2c_get_clientdata(client);
2018c2ecf20Sopenharmony_ci	int i;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(pismo->dev); i++)
2048c2ecf20Sopenharmony_ci		platform_device_unregister(pismo->dev[i]);
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	kfree(pismo);
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	return 0;
2098c2ecf20Sopenharmony_ci}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_cistatic int pismo_probe(struct i2c_client *client,
2128c2ecf20Sopenharmony_ci		       const struct i2c_device_id *id)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	struct pismo_pdata *pdata = client->dev.platform_data;
2158c2ecf20Sopenharmony_ci	struct pismo_eeprom eeprom;
2168c2ecf20Sopenharmony_ci	struct pismo_data *pismo;
2178c2ecf20Sopenharmony_ci	int ret, i;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
2208c2ecf20Sopenharmony_ci		dev_err(&client->dev, "functionality mismatch\n");
2218c2ecf20Sopenharmony_ci		return -EIO;
2228c2ecf20Sopenharmony_ci	}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	pismo = kzalloc(sizeof(*pismo), GFP_KERNEL);
2258c2ecf20Sopenharmony_ci	if (!pismo)
2268c2ecf20Sopenharmony_ci		return -ENOMEM;
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	pismo->client = client;
2298c2ecf20Sopenharmony_ci	if (pdata) {
2308c2ecf20Sopenharmony_ci		pismo->vpp = pdata->set_vpp;
2318c2ecf20Sopenharmony_ci		pismo->vpp_data = pdata->vpp_data;
2328c2ecf20Sopenharmony_ci	}
2338c2ecf20Sopenharmony_ci	i2c_set_clientdata(client, pismo);
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	ret = pismo_eeprom_read(client, &eeprom, 0, sizeof(eeprom));
2368c2ecf20Sopenharmony_ci	if (ret < 0) {
2378c2ecf20Sopenharmony_ci		dev_err(&client->dev, "error reading EEPROM: %d\n", ret);
2388c2ecf20Sopenharmony_ci		goto exit_free;
2398c2ecf20Sopenharmony_ci	}
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	dev_info(&client->dev, "%.15s board found\n", eeprom.board);
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(eeprom.cs); i++)
2448c2ecf20Sopenharmony_ci		if (eeprom.cs[i].type != 0xff)
2458c2ecf20Sopenharmony_ci			pismo_add_one(pismo, i, &eeprom.cs[i],
2468c2ecf20Sopenharmony_ci				      pdata->cs_addrs[i]);
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	return 0;
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci exit_free:
2518c2ecf20Sopenharmony_ci	kfree(pismo);
2528c2ecf20Sopenharmony_ci	return ret;
2538c2ecf20Sopenharmony_ci}
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_cistatic const struct i2c_device_id pismo_id[] = {
2568c2ecf20Sopenharmony_ci	{ "pismo" },
2578c2ecf20Sopenharmony_ci	{ },
2588c2ecf20Sopenharmony_ci};
2598c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, pismo_id);
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cistatic struct i2c_driver pismo_driver = {
2628c2ecf20Sopenharmony_ci	.driver	= {
2638c2ecf20Sopenharmony_ci		.name	= "pismo",
2648c2ecf20Sopenharmony_ci	},
2658c2ecf20Sopenharmony_ci	.probe		= pismo_probe,
2668c2ecf20Sopenharmony_ci	.remove		= pismo_remove,
2678c2ecf20Sopenharmony_ci	.id_table	= pismo_id,
2688c2ecf20Sopenharmony_ci};
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_cistatic int __init pismo_init(void)
2718c2ecf20Sopenharmony_ci{
2728c2ecf20Sopenharmony_ci	BUILD_BUG_ON(sizeof(struct pismo_cs_block) != 48);
2738c2ecf20Sopenharmony_ci	BUILD_BUG_ON(sizeof(struct pismo_eeprom) != 256);
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	return i2c_add_driver(&pismo_driver);
2768c2ecf20Sopenharmony_ci}
2778c2ecf20Sopenharmony_cimodule_init(pismo_init);
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_cistatic void __exit pismo_exit(void)
2808c2ecf20Sopenharmony_ci{
2818c2ecf20Sopenharmony_ci	i2c_del_driver(&pismo_driver);
2828c2ecf20Sopenharmony_ci}
2838c2ecf20Sopenharmony_cimodule_exit(pismo_exit);
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ciMODULE_AUTHOR("Russell King <linux@arm.linux.org.uk>");
2868c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PISMO memory driver");
2878c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
288