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, ®ion); 1908c2ecf20Sopenharmony_ci break; 1918c2ecf20Sopenharmony_ci case 3: 1928c2ecf20Sopenharmony_ci /* static RAM */ 1938c2ecf20Sopenharmony_ci pismo_add_sram(pismo, i, ®ion); 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