18d36fe1eSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 25dc17fa6SAndrew Lunn /* 35dc17fa6SAndrew Lunn * mchp23k256.c 45dc17fa6SAndrew Lunn * 55dc17fa6SAndrew Lunn * Driver for Microchip 23k256 SPI RAM chips 65dc17fa6SAndrew Lunn * 75dc17fa6SAndrew Lunn * Copyright © 2016 Andrew Lunn <andrew@lunn.ch> 85dc17fa6SAndrew Lunn */ 95dc17fa6SAndrew Lunn #include <linux/device.h> 105dc17fa6SAndrew Lunn #include <linux/module.h> 115dc17fa6SAndrew Lunn #include <linux/mtd/mtd.h> 125dc17fa6SAndrew Lunn #include <linux/mtd/partitions.h> 135dc17fa6SAndrew Lunn #include <linux/mutex.h> 145dc17fa6SAndrew Lunn #include <linux/sched.h> 155dc17fa6SAndrew Lunn #include <linux/sizes.h> 165dc17fa6SAndrew Lunn #include <linux/spi/flash.h> 175dc17fa6SAndrew Lunn #include <linux/spi/spi.h> 184db4d35eSChris Packham #include <linux/of_device.h> 195dc17fa6SAndrew Lunn 204379075aSChris Packham #define MAX_CMD_SIZE 4 214379075aSChris Packham 224379075aSChris Packham struct mchp23_caps { 234379075aSChris Packham u8 addr_width; 244379075aSChris Packham unsigned int size; 254379075aSChris Packham }; 264379075aSChris Packham 275dc17fa6SAndrew Lunn struct mchp23k256_flash { 285dc17fa6SAndrew Lunn struct spi_device *spi; 295dc17fa6SAndrew Lunn struct mutex lock; 305dc17fa6SAndrew Lunn struct mtd_info mtd; 314379075aSChris Packham const struct mchp23_caps *caps; 325dc17fa6SAndrew Lunn }; 335dc17fa6SAndrew Lunn 345dc17fa6SAndrew Lunn #define MCHP23K256_CMD_WRITE_STATUS 0x01 355dc17fa6SAndrew Lunn #define MCHP23K256_CMD_WRITE 0x02 365dc17fa6SAndrew Lunn #define MCHP23K256_CMD_READ 0x03 375dc17fa6SAndrew Lunn #define MCHP23K256_MODE_SEQ BIT(6) 385dc17fa6SAndrew Lunn 395dc17fa6SAndrew Lunn #define to_mchp23k256_flash(x) container_of(x, struct mchp23k256_flash, mtd) 405dc17fa6SAndrew Lunn 414379075aSChris Packham static void mchp23k256_addr2cmd(struct mchp23k256_flash *flash, 424379075aSChris Packham unsigned int addr, u8 *cmd) 434379075aSChris Packham { 444379075aSChris Packham int i; 454379075aSChris Packham 464379075aSChris Packham /* 474379075aSChris Packham * Address is sent in big endian (MSB first) and we skip 484379075aSChris Packham * the first entry of the cmd array which contains the cmd 494379075aSChris Packham * opcode. 504379075aSChris Packham */ 514379075aSChris Packham for (i = flash->caps->addr_width; i > 0; i--, addr >>= 8) 524379075aSChris Packham cmd[i] = addr; 534379075aSChris Packham } 544379075aSChris Packham 554379075aSChris Packham static int mchp23k256_cmdsz(struct mchp23k256_flash *flash) 564379075aSChris Packham { 574379075aSChris Packham return 1 + flash->caps->addr_width; 584379075aSChris Packham } 594379075aSChris Packham 605dc17fa6SAndrew Lunn static int mchp23k256_write(struct mtd_info *mtd, loff_t to, size_t len, 615dc17fa6SAndrew Lunn size_t *retlen, const unsigned char *buf) 625dc17fa6SAndrew Lunn { 635dc17fa6SAndrew Lunn struct mchp23k256_flash *flash = to_mchp23k256_flash(mtd); 645dc17fa6SAndrew Lunn struct spi_transfer transfer[2] = {}; 655dc17fa6SAndrew Lunn struct spi_message message; 664379075aSChris Packham unsigned char command[MAX_CMD_SIZE]; 6714f89e08SAngelo Dureghello int ret, cmd_len; 685dc17fa6SAndrew Lunn 695dc17fa6SAndrew Lunn spi_message_init(&message); 705dc17fa6SAndrew Lunn 7114f89e08SAngelo Dureghello cmd_len = mchp23k256_cmdsz(flash); 7214f89e08SAngelo Dureghello 735dc17fa6SAndrew Lunn command[0] = MCHP23K256_CMD_WRITE; 744379075aSChris Packham mchp23k256_addr2cmd(flash, to, command); 755dc17fa6SAndrew Lunn 765dc17fa6SAndrew Lunn transfer[0].tx_buf = command; 7714f89e08SAngelo Dureghello transfer[0].len = cmd_len; 785dc17fa6SAndrew Lunn spi_message_add_tail(&transfer[0], &message); 795dc17fa6SAndrew Lunn 805dc17fa6SAndrew Lunn transfer[1].tx_buf = buf; 815dc17fa6SAndrew Lunn transfer[1].len = len; 825dc17fa6SAndrew Lunn spi_message_add_tail(&transfer[1], &message); 835dc17fa6SAndrew Lunn 845dc17fa6SAndrew Lunn mutex_lock(&flash->lock); 855dc17fa6SAndrew Lunn 86db601f3aSAntonio Borneo ret = spi_sync(flash->spi, &message); 87db601f3aSAntonio Borneo 88db601f3aSAntonio Borneo mutex_unlock(&flash->lock); 89db601f3aSAntonio Borneo 90db601f3aSAntonio Borneo if (ret) 91db601f3aSAntonio Borneo return ret; 925dc17fa6SAndrew Lunn 9314f89e08SAngelo Dureghello if (retlen && message.actual_length > cmd_len) 9414f89e08SAngelo Dureghello *retlen += message.actual_length - cmd_len; 955dc17fa6SAndrew Lunn 965dc17fa6SAndrew Lunn return 0; 975dc17fa6SAndrew Lunn } 985dc17fa6SAndrew Lunn 995dc17fa6SAndrew Lunn static int mchp23k256_read(struct mtd_info *mtd, loff_t from, size_t len, 1005dc17fa6SAndrew Lunn size_t *retlen, unsigned char *buf) 1015dc17fa6SAndrew Lunn { 1025dc17fa6SAndrew Lunn struct mchp23k256_flash *flash = to_mchp23k256_flash(mtd); 1035dc17fa6SAndrew Lunn struct spi_transfer transfer[2] = {}; 1045dc17fa6SAndrew Lunn struct spi_message message; 1054379075aSChris Packham unsigned char command[MAX_CMD_SIZE]; 10614f89e08SAngelo Dureghello int ret, cmd_len; 1075dc17fa6SAndrew Lunn 1085dc17fa6SAndrew Lunn spi_message_init(&message); 1095dc17fa6SAndrew Lunn 11014f89e08SAngelo Dureghello cmd_len = mchp23k256_cmdsz(flash); 11114f89e08SAngelo Dureghello 1125dc17fa6SAndrew Lunn memset(&transfer, 0, sizeof(transfer)); 1135dc17fa6SAndrew Lunn command[0] = MCHP23K256_CMD_READ; 1144379075aSChris Packham mchp23k256_addr2cmd(flash, from, command); 1155dc17fa6SAndrew Lunn 1165dc17fa6SAndrew Lunn transfer[0].tx_buf = command; 11714f89e08SAngelo Dureghello transfer[0].len = cmd_len; 1185dc17fa6SAndrew Lunn spi_message_add_tail(&transfer[0], &message); 1195dc17fa6SAndrew Lunn 1205dc17fa6SAndrew Lunn transfer[1].rx_buf = buf; 1215dc17fa6SAndrew Lunn transfer[1].len = len; 1225dc17fa6SAndrew Lunn spi_message_add_tail(&transfer[1], &message); 1235dc17fa6SAndrew Lunn 1245dc17fa6SAndrew Lunn mutex_lock(&flash->lock); 1255dc17fa6SAndrew Lunn 126db601f3aSAntonio Borneo ret = spi_sync(flash->spi, &message); 127db601f3aSAntonio Borneo 128db601f3aSAntonio Borneo mutex_unlock(&flash->lock); 129db601f3aSAntonio Borneo 130db601f3aSAntonio Borneo if (ret) 131db601f3aSAntonio Borneo return ret; 1325dc17fa6SAndrew Lunn 13314f89e08SAngelo Dureghello if (retlen && message.actual_length > cmd_len) 13414f89e08SAngelo Dureghello *retlen += message.actual_length - cmd_len; 1355dc17fa6SAndrew Lunn 1365dc17fa6SAndrew Lunn return 0; 1375dc17fa6SAndrew Lunn } 1385dc17fa6SAndrew Lunn 1395dc17fa6SAndrew Lunn /* 1405dc17fa6SAndrew Lunn * Set the device into sequential mode. This allows read/writes to the 1415dc17fa6SAndrew Lunn * entire SRAM in a single operation 1425dc17fa6SAndrew Lunn */ 1435dc17fa6SAndrew Lunn static int mchp23k256_set_mode(struct spi_device *spi) 1445dc17fa6SAndrew Lunn { 1455dc17fa6SAndrew Lunn struct spi_transfer transfer = {}; 1465dc17fa6SAndrew Lunn struct spi_message message; 1475dc17fa6SAndrew Lunn unsigned char command[2]; 1485dc17fa6SAndrew Lunn 1495dc17fa6SAndrew Lunn spi_message_init(&message); 1505dc17fa6SAndrew Lunn 1515dc17fa6SAndrew Lunn command[0] = MCHP23K256_CMD_WRITE_STATUS; 1525dc17fa6SAndrew Lunn command[1] = MCHP23K256_MODE_SEQ; 1535dc17fa6SAndrew Lunn 1545dc17fa6SAndrew Lunn transfer.tx_buf = command; 1555dc17fa6SAndrew Lunn transfer.len = sizeof(command); 1565dc17fa6SAndrew Lunn spi_message_add_tail(&transfer, &message); 1575dc17fa6SAndrew Lunn 1585dc17fa6SAndrew Lunn return spi_sync(spi, &message); 1595dc17fa6SAndrew Lunn } 1605dc17fa6SAndrew Lunn 1614379075aSChris Packham static const struct mchp23_caps mchp23k256_caps = { 1624379075aSChris Packham .size = SZ_32K, 1634379075aSChris Packham .addr_width = 2, 1644379075aSChris Packham }; 1654379075aSChris Packham 1664379075aSChris Packham static const struct mchp23_caps mchp23lcv1024_caps = { 1674379075aSChris Packham .size = SZ_128K, 1684379075aSChris Packham .addr_width = 3, 1694379075aSChris Packham }; 1704379075aSChris Packham 1715dc17fa6SAndrew Lunn static int mchp23k256_probe(struct spi_device *spi) 1725dc17fa6SAndrew Lunn { 1735dc17fa6SAndrew Lunn struct mchp23k256_flash *flash; 1745dc17fa6SAndrew Lunn struct flash_platform_data *data; 1755dc17fa6SAndrew Lunn int err; 1765dc17fa6SAndrew Lunn 1775dc17fa6SAndrew Lunn flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL); 1785dc17fa6SAndrew Lunn if (!flash) 1795dc17fa6SAndrew Lunn return -ENOMEM; 1805dc17fa6SAndrew Lunn 1815dc17fa6SAndrew Lunn flash->spi = spi; 1825dc17fa6SAndrew Lunn mutex_init(&flash->lock); 1835dc17fa6SAndrew Lunn spi_set_drvdata(spi, flash); 1845dc17fa6SAndrew Lunn 1855dc17fa6SAndrew Lunn err = mchp23k256_set_mode(spi); 1865dc17fa6SAndrew Lunn if (err) 1875dc17fa6SAndrew Lunn return err; 1885dc17fa6SAndrew Lunn 1895dc17fa6SAndrew Lunn data = dev_get_platdata(&spi->dev); 1905dc17fa6SAndrew Lunn 1914379075aSChris Packham flash->caps = of_device_get_match_data(&spi->dev); 1924379075aSChris Packham if (!flash->caps) 1934379075aSChris Packham flash->caps = &mchp23k256_caps; 1944379075aSChris Packham 1952f071ff1SChris Packham mtd_set_of_node(&flash->mtd, spi->dev.of_node); 1965dc17fa6SAndrew Lunn flash->mtd.dev.parent = &spi->dev; 1975dc17fa6SAndrew Lunn flash->mtd.type = MTD_RAM; 1985dc17fa6SAndrew Lunn flash->mtd.flags = MTD_CAP_RAM; 1995dc17fa6SAndrew Lunn flash->mtd.writesize = 1; 2004379075aSChris Packham flash->mtd.size = flash->caps->size; 2015dc17fa6SAndrew Lunn flash->mtd._read = mchp23k256_read; 2025dc17fa6SAndrew Lunn flash->mtd._write = mchp23k256_write; 2035dc17fa6SAndrew Lunn 20444225c9cSChris Packham err = mtd_device_register(&flash->mtd, data ? data->parts : NULL, 2055dc17fa6SAndrew Lunn data ? data->nr_parts : 0); 2065dc17fa6SAndrew Lunn if (err) 2075dc17fa6SAndrew Lunn return err; 2085dc17fa6SAndrew Lunn 2095dc17fa6SAndrew Lunn return 0; 2105dc17fa6SAndrew Lunn } 2115dc17fa6SAndrew Lunn 2125dc17fa6SAndrew Lunn static int mchp23k256_remove(struct spi_device *spi) 2135dc17fa6SAndrew Lunn { 2145dc17fa6SAndrew Lunn struct mchp23k256_flash *flash = spi_get_drvdata(spi); 2155dc17fa6SAndrew Lunn 2165dc17fa6SAndrew Lunn return mtd_device_unregister(&flash->mtd); 2175dc17fa6SAndrew Lunn } 2185dc17fa6SAndrew Lunn 2194db4d35eSChris Packham static const struct of_device_id mchp23k256_of_table[] = { 2204379075aSChris Packham { 2214379075aSChris Packham .compatible = "microchip,mchp23k256", 2224379075aSChris Packham .data = &mchp23k256_caps, 2234379075aSChris Packham }, 2244379075aSChris Packham { 2254379075aSChris Packham .compatible = "microchip,mchp23lcv1024", 2264379075aSChris Packham .data = &mchp23lcv1024_caps, 2274379075aSChris Packham }, 2284db4d35eSChris Packham {} 2294db4d35eSChris Packham }; 2304db4d35eSChris Packham MODULE_DEVICE_TABLE(of, mchp23k256_of_table); 2314db4d35eSChris Packham 2325dc17fa6SAndrew Lunn static struct spi_driver mchp23k256_driver = { 2335dc17fa6SAndrew Lunn .driver = { 2345dc17fa6SAndrew Lunn .name = "mchp23k256", 2354db4d35eSChris Packham .of_match_table = of_match_ptr(mchp23k256_of_table), 2365dc17fa6SAndrew Lunn }, 2375dc17fa6SAndrew Lunn .probe = mchp23k256_probe, 2385dc17fa6SAndrew Lunn .remove = mchp23k256_remove, 2395dc17fa6SAndrew Lunn }; 2405dc17fa6SAndrew Lunn 2415dc17fa6SAndrew Lunn module_spi_driver(mchp23k256_driver); 2425dc17fa6SAndrew Lunn 2435dc17fa6SAndrew Lunn MODULE_DESCRIPTION("MTD SPI driver for MCHP23K256 RAM chips"); 2445dc17fa6SAndrew Lunn MODULE_AUTHOR("Andrew Lunn <andre@lunn.ch>"); 2455dc17fa6SAndrew Lunn MODULE_LICENSE("GPL v2"); 2465dc17fa6SAndrew Lunn MODULE_ALIAS("spi:mchp23k256"); 247