162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PISMO memory driver - http://www.pismoworld.org/ 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * For ARM Realview and Versatile platforms 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci#include <linux/init.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/i2c.h> 1062306a36Sopenharmony_ci#include <linux/slab.h> 1162306a36Sopenharmony_ci#include <linux/platform_device.h> 1262306a36Sopenharmony_ci#include <linux/spinlock.h> 1362306a36Sopenharmony_ci#include <linux/mutex.h> 1462306a36Sopenharmony_ci#include <linux/mtd/physmap.h> 1562306a36Sopenharmony_ci#include <linux/mtd/plat-ram.h> 1662306a36Sopenharmony_ci#include <linux/mtd/pismo.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define PISMO_NUM_CS 5 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistruct pismo_cs_block { 2162306a36Sopenharmony_ci u8 type; 2262306a36Sopenharmony_ci u8 width; 2362306a36Sopenharmony_ci __le16 access; 2462306a36Sopenharmony_ci __le32 size; 2562306a36Sopenharmony_ci u32 reserved[2]; 2662306a36Sopenharmony_ci char device[32]; 2762306a36Sopenharmony_ci} __packed; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistruct pismo_eeprom { 3062306a36Sopenharmony_ci struct pismo_cs_block cs[PISMO_NUM_CS]; 3162306a36Sopenharmony_ci char board[15]; 3262306a36Sopenharmony_ci u8 sum; 3362306a36Sopenharmony_ci} __packed; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistruct pismo_mem { 3662306a36Sopenharmony_ci phys_addr_t base; 3762306a36Sopenharmony_ci u32 size; 3862306a36Sopenharmony_ci u16 access; 3962306a36Sopenharmony_ci u8 width; 4062306a36Sopenharmony_ci u8 type; 4162306a36Sopenharmony_ci}; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistruct pismo_data { 4462306a36Sopenharmony_ci struct i2c_client *client; 4562306a36Sopenharmony_ci void (*vpp)(void *, int); 4662306a36Sopenharmony_ci void *vpp_data; 4762306a36Sopenharmony_ci struct platform_device *dev[PISMO_NUM_CS]; 4862306a36Sopenharmony_ci}; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic void pismo_set_vpp(struct platform_device *pdev, int on) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci struct i2c_client *client = to_i2c_client(pdev->dev.parent); 5362306a36Sopenharmony_ci struct pismo_data *pismo = i2c_get_clientdata(client); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci pismo->vpp(pismo->vpp_data, on); 5662306a36Sopenharmony_ci} 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic unsigned int pismo_width_to_bytes(unsigned int width) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci width &= 15; 6162306a36Sopenharmony_ci if (width > 2) 6262306a36Sopenharmony_ci return 0; 6362306a36Sopenharmony_ci return 1 << width; 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic int pismo_eeprom_read(struct i2c_client *client, void *buf, u8 addr, 6762306a36Sopenharmony_ci size_t size) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci int ret; 7062306a36Sopenharmony_ci struct i2c_msg msg[] = { 7162306a36Sopenharmony_ci { 7262306a36Sopenharmony_ci .addr = client->addr, 7362306a36Sopenharmony_ci .len = sizeof(addr), 7462306a36Sopenharmony_ci .buf = &addr, 7562306a36Sopenharmony_ci }, { 7662306a36Sopenharmony_ci .addr = client->addr, 7762306a36Sopenharmony_ci .flags = I2C_M_RD, 7862306a36Sopenharmony_ci .len = size, 7962306a36Sopenharmony_ci .buf = buf, 8062306a36Sopenharmony_ci }, 8162306a36Sopenharmony_ci }; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci return ret == ARRAY_SIZE(msg) ? size : -EIO; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic int pismo_add_device(struct pismo_data *pismo, int i, 8962306a36Sopenharmony_ci struct pismo_mem *region, const char *name, 9062306a36Sopenharmony_ci void *pdata, size_t psize) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci struct platform_device *dev; 9362306a36Sopenharmony_ci struct resource res = { }; 9462306a36Sopenharmony_ci phys_addr_t base = region->base; 9562306a36Sopenharmony_ci int ret; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci if (base == ~0) 9862306a36Sopenharmony_ci return -ENXIO; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci res.start = base; 10162306a36Sopenharmony_ci res.end = base + region->size - 1; 10262306a36Sopenharmony_ci res.flags = IORESOURCE_MEM; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci dev = platform_device_alloc(name, i); 10562306a36Sopenharmony_ci if (!dev) 10662306a36Sopenharmony_ci return -ENOMEM; 10762306a36Sopenharmony_ci dev->dev.parent = &pismo->client->dev; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci do { 11062306a36Sopenharmony_ci ret = platform_device_add_resources(dev, &res, 1); 11162306a36Sopenharmony_ci if (ret) 11262306a36Sopenharmony_ci break; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci ret = platform_device_add_data(dev, pdata, psize); 11562306a36Sopenharmony_ci if (ret) 11662306a36Sopenharmony_ci break; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci ret = platform_device_add(dev); 11962306a36Sopenharmony_ci if (ret) 12062306a36Sopenharmony_ci break; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci pismo->dev[i] = dev; 12362306a36Sopenharmony_ci return 0; 12462306a36Sopenharmony_ci } while (0); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci platform_device_put(dev); 12762306a36Sopenharmony_ci return ret; 12862306a36Sopenharmony_ci} 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic int pismo_add_nor(struct pismo_data *pismo, int i, 13162306a36Sopenharmony_ci struct pismo_mem *region) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci struct physmap_flash_data data = { 13462306a36Sopenharmony_ci .width = region->width, 13562306a36Sopenharmony_ci }; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if (pismo->vpp) 13862306a36Sopenharmony_ci data.set_vpp = pismo_set_vpp; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return pismo_add_device(pismo, i, region, "physmap-flash", 14162306a36Sopenharmony_ci &data, sizeof(data)); 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic int pismo_add_sram(struct pismo_data *pismo, int i, 14562306a36Sopenharmony_ci struct pismo_mem *region) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci struct platdata_mtd_ram data = { 14862306a36Sopenharmony_ci .bankwidth = region->width, 14962306a36Sopenharmony_ci }; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci return pismo_add_device(pismo, i, region, "mtd-ram", 15262306a36Sopenharmony_ci &data, sizeof(data)); 15362306a36Sopenharmony_ci} 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_cistatic void pismo_add_one(struct pismo_data *pismo, int i, 15662306a36Sopenharmony_ci const struct pismo_cs_block *cs, phys_addr_t base) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci struct device *dev = &pismo->client->dev; 15962306a36Sopenharmony_ci struct pismo_mem region; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci region.base = base; 16262306a36Sopenharmony_ci region.type = cs->type; 16362306a36Sopenharmony_ci region.width = pismo_width_to_bytes(cs->width); 16462306a36Sopenharmony_ci region.access = le16_to_cpu(cs->access); 16562306a36Sopenharmony_ci region.size = le32_to_cpu(cs->size); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci if (region.width == 0) { 16862306a36Sopenharmony_ci dev_err(dev, "cs%u: bad width: %02x, ignoring\n", i, cs->width); 16962306a36Sopenharmony_ci return; 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci /* 17362306a36Sopenharmony_ci * FIXME: may need to the platforms memory controller here, but at 17462306a36Sopenharmony_ci * the moment we assume that it has already been correctly setup. 17562306a36Sopenharmony_ci * The memory controller can also tell us the base address as well. 17662306a36Sopenharmony_ci */ 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci dev_info(dev, "cs%u: %.32s: type %02x access %u00ps size %uK\n", 17962306a36Sopenharmony_ci i, cs->device, region.type, region.access, region.size / 1024); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci switch (region.type) { 18262306a36Sopenharmony_ci case 0: 18362306a36Sopenharmony_ci break; 18462306a36Sopenharmony_ci case 1: 18562306a36Sopenharmony_ci /* static DOC */ 18662306a36Sopenharmony_ci break; 18762306a36Sopenharmony_ci case 2: 18862306a36Sopenharmony_ci /* static NOR */ 18962306a36Sopenharmony_ci pismo_add_nor(pismo, i, ®ion); 19062306a36Sopenharmony_ci break; 19162306a36Sopenharmony_ci case 3: 19262306a36Sopenharmony_ci /* static RAM */ 19362306a36Sopenharmony_ci pismo_add_sram(pismo, i, ®ion); 19462306a36Sopenharmony_ci break; 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic void pismo_remove(struct i2c_client *client) 19962306a36Sopenharmony_ci{ 20062306a36Sopenharmony_ci struct pismo_data *pismo = i2c_get_clientdata(client); 20162306a36Sopenharmony_ci int i; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(pismo->dev); i++) 20462306a36Sopenharmony_ci platform_device_unregister(pismo->dev[i]); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci kfree(pismo); 20762306a36Sopenharmony_ci} 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_cistatic int pismo_probe(struct i2c_client *client) 21062306a36Sopenharmony_ci{ 21162306a36Sopenharmony_ci struct pismo_pdata *pdata = client->dev.platform_data; 21262306a36Sopenharmony_ci struct pismo_eeprom eeprom; 21362306a36Sopenharmony_ci struct pismo_data *pismo; 21462306a36Sopenharmony_ci int ret, i; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { 21762306a36Sopenharmony_ci dev_err(&client->dev, "functionality mismatch\n"); 21862306a36Sopenharmony_ci return -EIO; 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci pismo = kzalloc(sizeof(*pismo), GFP_KERNEL); 22262306a36Sopenharmony_ci if (!pismo) 22362306a36Sopenharmony_ci return -ENOMEM; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci pismo->client = client; 22662306a36Sopenharmony_ci if (pdata) { 22762306a36Sopenharmony_ci pismo->vpp = pdata->set_vpp; 22862306a36Sopenharmony_ci pismo->vpp_data = pdata->vpp_data; 22962306a36Sopenharmony_ci } 23062306a36Sopenharmony_ci i2c_set_clientdata(client, pismo); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci ret = pismo_eeprom_read(client, &eeprom, 0, sizeof(eeprom)); 23362306a36Sopenharmony_ci if (ret < 0) { 23462306a36Sopenharmony_ci dev_err(&client->dev, "error reading EEPROM: %d\n", ret); 23562306a36Sopenharmony_ci goto exit_free; 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci dev_info(&client->dev, "%.15s board found\n", eeprom.board); 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(eeprom.cs); i++) 24162306a36Sopenharmony_ci if (eeprom.cs[i].type != 0xff) 24262306a36Sopenharmony_ci pismo_add_one(pismo, i, &eeprom.cs[i], 24362306a36Sopenharmony_ci pdata->cs_addrs[i]); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci return 0; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci exit_free: 24862306a36Sopenharmony_ci kfree(pismo); 24962306a36Sopenharmony_ci return ret; 25062306a36Sopenharmony_ci} 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_cistatic const struct i2c_device_id pismo_id[] = { 25362306a36Sopenharmony_ci { "pismo" }, 25462306a36Sopenharmony_ci { }, 25562306a36Sopenharmony_ci}; 25662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, pismo_id); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_cistatic struct i2c_driver pismo_driver = { 25962306a36Sopenharmony_ci .driver = { 26062306a36Sopenharmony_ci .name = "pismo", 26162306a36Sopenharmony_ci }, 26262306a36Sopenharmony_ci .probe = pismo_probe, 26362306a36Sopenharmony_ci .remove = pismo_remove, 26462306a36Sopenharmony_ci .id_table = pismo_id, 26562306a36Sopenharmony_ci}; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_cistatic int __init pismo_init(void) 26862306a36Sopenharmony_ci{ 26962306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(struct pismo_cs_block) != 48); 27062306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(struct pismo_eeprom) != 256); 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci return i2c_add_driver(&pismo_driver); 27362306a36Sopenharmony_ci} 27462306a36Sopenharmony_cimodule_init(pismo_init); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_cistatic void __exit pismo_exit(void) 27762306a36Sopenharmony_ci{ 27862306a36Sopenharmony_ci i2c_del_driver(&pismo_driver); 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_cimodule_exit(pismo_exit); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ciMODULE_AUTHOR("Russell King <linux@arm.linux.org.uk>"); 28362306a36Sopenharmony_ciMODULE_DESCRIPTION("PISMO memory driver"); 28462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 285