xref: /openbmc/linux/drivers/spi/spi-wpcm-fiu.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
11f8811a2SJonathan Neuschäfer // SPDX-License-Identifier: GPL-2.0
21f8811a2SJonathan Neuschäfer // Copyright (C) 2022 Jonathan Neuschäfer
31f8811a2SJonathan Neuschäfer 
41f8811a2SJonathan Neuschäfer #include <linux/clk.h>
51f8811a2SJonathan Neuschäfer #include <linux/mfd/syscon.h>
6*749396cbSRob Herring #include <linux/mod_devicetable.h>
71f8811a2SJonathan Neuschäfer #include <linux/module.h>
81f8811a2SJonathan Neuschäfer #include <linux/platform_device.h>
91f8811a2SJonathan Neuschäfer #include <linux/regmap.h>
101f8811a2SJonathan Neuschäfer #include <linux/spi/spi-mem.h>
111f8811a2SJonathan Neuschäfer 
121f8811a2SJonathan Neuschäfer #define FIU_CFG		0x00
131f8811a2SJonathan Neuschäfer #define FIU_BURST_BFG	0x01
141f8811a2SJonathan Neuschäfer #define FIU_RESP_CFG	0x02
151f8811a2SJonathan Neuschäfer #define FIU_CFBB_PROT	0x03
161f8811a2SJonathan Neuschäfer #define FIU_FWIN1_LOW	0x04
171f8811a2SJonathan Neuschäfer #define FIU_FWIN1_HIGH	0x06
181f8811a2SJonathan Neuschäfer #define FIU_FWIN2_LOW	0x08
191f8811a2SJonathan Neuschäfer #define FIU_FWIN2_HIGH	0x0a
201f8811a2SJonathan Neuschäfer #define FIU_FWIN3_LOW	0x0c
211f8811a2SJonathan Neuschäfer #define FIU_FWIN3_HIGH	0x0e
221f8811a2SJonathan Neuschäfer #define FIU_PROT_LOCK	0x10
231f8811a2SJonathan Neuschäfer #define FIU_PROT_CLEAR	0x11
241f8811a2SJonathan Neuschäfer #define FIU_SPI_FL_CFG	0x14
251f8811a2SJonathan Neuschäfer #define FIU_UMA_CODE	0x16
261f8811a2SJonathan Neuschäfer #define FIU_UMA_AB0	0x17
271f8811a2SJonathan Neuschäfer #define FIU_UMA_AB1	0x18
281f8811a2SJonathan Neuschäfer #define FIU_UMA_AB2	0x19
291f8811a2SJonathan Neuschäfer #define FIU_UMA_DB0	0x1a
301f8811a2SJonathan Neuschäfer #define FIU_UMA_DB1	0x1b
311f8811a2SJonathan Neuschäfer #define FIU_UMA_DB2	0x1c
321f8811a2SJonathan Neuschäfer #define FIU_UMA_DB3	0x1d
331f8811a2SJonathan Neuschäfer #define FIU_UMA_CTS	0x1e
341f8811a2SJonathan Neuschäfer #define FIU_UMA_ECTS	0x1f
351f8811a2SJonathan Neuschäfer 
361f8811a2SJonathan Neuschäfer #define FIU_BURST_CFG_R16	3
371f8811a2SJonathan Neuschäfer 
381f8811a2SJonathan Neuschäfer #define FIU_UMA_CTS_D_SIZE(x)	(x)
391f8811a2SJonathan Neuschäfer #define FIU_UMA_CTS_A_SIZE	BIT(3)
401f8811a2SJonathan Neuschäfer #define FIU_UMA_CTS_WR		BIT(4)
411f8811a2SJonathan Neuschäfer #define FIU_UMA_CTS_CS(x)	((x) << 5)
421f8811a2SJonathan Neuschäfer #define FIU_UMA_CTS_EXEC_DONE	BIT(7)
431f8811a2SJonathan Neuschäfer 
441f8811a2SJonathan Neuschäfer #define SHM_FLASH_SIZE	0x02
451f8811a2SJonathan Neuschäfer #define SHM_FLASH_SIZE_STALL_HOST BIT(6)
461f8811a2SJonathan Neuschäfer 
471f8811a2SJonathan Neuschäfer /*
481f8811a2SJonathan Neuschäfer  * I observed a typical wait time of 16 iterations for a UMA transfer to
491f8811a2SJonathan Neuschäfer  * finish, so this should be a safe limit.
501f8811a2SJonathan Neuschäfer  */
511f8811a2SJonathan Neuschäfer #define UMA_WAIT_ITERATIONS 100
521f8811a2SJonathan Neuschäfer 
539838c182SJonathan Neuschäfer /* The memory-mapped view of flash is 16 MiB long */
549838c182SJonathan Neuschäfer #define MAX_MEMORY_SIZE_PER_CS	(16 << 20)
559838c182SJonathan Neuschäfer #define MAX_MEMORY_SIZE_TOTAL	(4 * MAX_MEMORY_SIZE_PER_CS)
569838c182SJonathan Neuschäfer 
571f8811a2SJonathan Neuschäfer struct wpcm_fiu_spi {
581f8811a2SJonathan Neuschäfer 	struct device *dev;
591f8811a2SJonathan Neuschäfer 	struct clk *clk;
601f8811a2SJonathan Neuschäfer 	void __iomem *regs;
619838c182SJonathan Neuschäfer 	void __iomem *memory;
629838c182SJonathan Neuschäfer 	size_t memory_size;
631f8811a2SJonathan Neuschäfer 	struct regmap *shm_regmap;
641f8811a2SJonathan Neuschäfer };
651f8811a2SJonathan Neuschäfer 
wpcm_fiu_set_opcode(struct wpcm_fiu_spi * fiu,u8 opcode)661f8811a2SJonathan Neuschäfer static void wpcm_fiu_set_opcode(struct wpcm_fiu_spi *fiu, u8 opcode)
671f8811a2SJonathan Neuschäfer {
681f8811a2SJonathan Neuschäfer 	writeb(opcode, fiu->regs + FIU_UMA_CODE);
691f8811a2SJonathan Neuschäfer }
701f8811a2SJonathan Neuschäfer 
wpcm_fiu_set_addr(struct wpcm_fiu_spi * fiu,u32 addr)711f8811a2SJonathan Neuschäfer static void wpcm_fiu_set_addr(struct wpcm_fiu_spi *fiu, u32 addr)
721f8811a2SJonathan Neuschäfer {
731f8811a2SJonathan Neuschäfer 	writeb((addr >>  0) & 0xff, fiu->regs + FIU_UMA_AB0);
741f8811a2SJonathan Neuschäfer 	writeb((addr >>  8) & 0xff, fiu->regs + FIU_UMA_AB1);
751f8811a2SJonathan Neuschäfer 	writeb((addr >> 16) & 0xff, fiu->regs + FIU_UMA_AB2);
761f8811a2SJonathan Neuschäfer }
771f8811a2SJonathan Neuschäfer 
wpcm_fiu_set_data(struct wpcm_fiu_spi * fiu,const u8 * data,unsigned int nbytes)781f8811a2SJonathan Neuschäfer static void wpcm_fiu_set_data(struct wpcm_fiu_spi *fiu, const u8 *data, unsigned int nbytes)
791f8811a2SJonathan Neuschäfer {
801f8811a2SJonathan Neuschäfer 	int i;
811f8811a2SJonathan Neuschäfer 
821f8811a2SJonathan Neuschäfer 	for (i = 0; i < nbytes; i++)
831f8811a2SJonathan Neuschäfer 		writeb(data[i], fiu->regs + FIU_UMA_DB0 + i);
841f8811a2SJonathan Neuschäfer }
851f8811a2SJonathan Neuschäfer 
wpcm_fiu_get_data(struct wpcm_fiu_spi * fiu,u8 * data,unsigned int nbytes)861f8811a2SJonathan Neuschäfer static void wpcm_fiu_get_data(struct wpcm_fiu_spi *fiu, u8 *data, unsigned int nbytes)
871f8811a2SJonathan Neuschäfer {
881f8811a2SJonathan Neuschäfer 	int i;
891f8811a2SJonathan Neuschäfer 
901f8811a2SJonathan Neuschäfer 	for (i = 0; i < nbytes; i++)
911f8811a2SJonathan Neuschäfer 		data[i] = readb(fiu->regs + FIU_UMA_DB0 + i);
921f8811a2SJonathan Neuschäfer }
931f8811a2SJonathan Neuschäfer 
941f8811a2SJonathan Neuschäfer /*
951f8811a2SJonathan Neuschäfer  * Perform a UMA (User Mode Access) operation, i.e. a software-controlled SPI transfer.
961f8811a2SJonathan Neuschäfer  */
wpcm_fiu_do_uma(struct wpcm_fiu_spi * fiu,unsigned int cs,bool use_addr,bool write,int data_bytes)971f8811a2SJonathan Neuschäfer static int wpcm_fiu_do_uma(struct wpcm_fiu_spi *fiu, unsigned int cs,
981f8811a2SJonathan Neuschäfer 			   bool use_addr, bool write, int data_bytes)
991f8811a2SJonathan Neuschäfer {
1001f8811a2SJonathan Neuschäfer 	int i = 0;
1011f8811a2SJonathan Neuschäfer 	u8 cts = FIU_UMA_CTS_EXEC_DONE | FIU_UMA_CTS_CS(cs);
1021f8811a2SJonathan Neuschäfer 
1031f8811a2SJonathan Neuschäfer 	if (use_addr)
1041f8811a2SJonathan Neuschäfer 		cts |= FIU_UMA_CTS_A_SIZE;
1051f8811a2SJonathan Neuschäfer 	if (write)
1061f8811a2SJonathan Neuschäfer 		cts |= FIU_UMA_CTS_WR;
1071f8811a2SJonathan Neuschäfer 	cts |= FIU_UMA_CTS_D_SIZE(data_bytes);
1081f8811a2SJonathan Neuschäfer 
1091f8811a2SJonathan Neuschäfer 	writeb(cts, fiu->regs + FIU_UMA_CTS);
1101f8811a2SJonathan Neuschäfer 
1111f8811a2SJonathan Neuschäfer 	for (i = 0; i < UMA_WAIT_ITERATIONS; i++)
1121f8811a2SJonathan Neuschäfer 		if (!(readb(fiu->regs + FIU_UMA_CTS) & FIU_UMA_CTS_EXEC_DONE))
1131f8811a2SJonathan Neuschäfer 			return 0;
1141f8811a2SJonathan Neuschäfer 
1151f8811a2SJonathan Neuschäfer 	dev_info(fiu->dev, "UMA transfer has not finished in %d iterations\n", UMA_WAIT_ITERATIONS);
1161f8811a2SJonathan Neuschäfer 	return -EIO;
1171f8811a2SJonathan Neuschäfer }
1181f8811a2SJonathan Neuschäfer 
wpcm_fiu_ects_assert(struct wpcm_fiu_spi * fiu,unsigned int cs)1191f8811a2SJonathan Neuschäfer static void wpcm_fiu_ects_assert(struct wpcm_fiu_spi *fiu, unsigned int cs)
1201f8811a2SJonathan Neuschäfer {
1211f8811a2SJonathan Neuschäfer 	u8 ects = readb(fiu->regs + FIU_UMA_ECTS);
1221f8811a2SJonathan Neuschäfer 
1231f8811a2SJonathan Neuschäfer 	ects &= ~BIT(cs);
1241f8811a2SJonathan Neuschäfer 	writeb(ects, fiu->regs + FIU_UMA_ECTS);
1251f8811a2SJonathan Neuschäfer }
1261f8811a2SJonathan Neuschäfer 
wpcm_fiu_ects_deassert(struct wpcm_fiu_spi * fiu,unsigned int cs)1271f8811a2SJonathan Neuschäfer static void wpcm_fiu_ects_deassert(struct wpcm_fiu_spi *fiu, unsigned int cs)
1281f8811a2SJonathan Neuschäfer {
1291f8811a2SJonathan Neuschäfer 	u8 ects = readb(fiu->regs + FIU_UMA_ECTS);
1301f8811a2SJonathan Neuschäfer 
1311f8811a2SJonathan Neuschäfer 	ects |= BIT(cs);
1321f8811a2SJonathan Neuschäfer 	writeb(ects, fiu->regs + FIU_UMA_ECTS);
1331f8811a2SJonathan Neuschäfer }
1341f8811a2SJonathan Neuschäfer 
1351f8811a2SJonathan Neuschäfer struct wpcm_fiu_op_shape {
1361f8811a2SJonathan Neuschäfer 	bool (*match)(const struct spi_mem_op *op);
1371f8811a2SJonathan Neuschäfer 	int (*exec)(struct spi_mem *mem, const struct spi_mem_op *op);
1381f8811a2SJonathan Neuschäfer };
1391f8811a2SJonathan Neuschäfer 
wpcm_fiu_normal_match(const struct spi_mem_op * op)1401f8811a2SJonathan Neuschäfer static bool wpcm_fiu_normal_match(const struct spi_mem_op *op)
1411f8811a2SJonathan Neuschäfer {
1421f8811a2SJonathan Neuschäfer 	// Opcode 0x0b (FAST READ) is treated differently in hardware
1431f8811a2SJonathan Neuschäfer 	if (op->cmd.opcode == 0x0b)
1441f8811a2SJonathan Neuschäfer 		return false;
1451f8811a2SJonathan Neuschäfer 
1461f8811a2SJonathan Neuschäfer 	return (op->addr.nbytes == 0 || op->addr.nbytes == 3) &&
1471f8811a2SJonathan Neuschäfer 	       op->dummy.nbytes == 0 && op->data.nbytes <= 4;
1481f8811a2SJonathan Neuschäfer }
1491f8811a2SJonathan Neuschäfer 
wpcm_fiu_normal_exec(struct spi_mem * mem,const struct spi_mem_op * op)1501f8811a2SJonathan Neuschäfer static int wpcm_fiu_normal_exec(struct spi_mem *mem, const struct spi_mem_op *op)
1511f8811a2SJonathan Neuschäfer {
1521f8811a2SJonathan Neuschäfer 	struct wpcm_fiu_spi *fiu = spi_controller_get_devdata(mem->spi->controller);
1531f8811a2SJonathan Neuschäfer 	int ret;
1541f8811a2SJonathan Neuschäfer 
1551f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_opcode(fiu, op->cmd.opcode);
1561f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_addr(fiu, op->addr.val);
1571f8811a2SJonathan Neuschäfer 	if (op->data.dir == SPI_MEM_DATA_OUT)
1581f8811a2SJonathan Neuschäfer 		wpcm_fiu_set_data(fiu, op->data.buf.out, op->data.nbytes);
1591f8811a2SJonathan Neuschäfer 
1609e264f3fSAmit Kumar Mahapatra via Alsa-devel 	ret = wpcm_fiu_do_uma(fiu, spi_get_chipselect(mem->spi, 0), op->addr.nbytes == 3,
1611f8811a2SJonathan Neuschäfer 			      op->data.dir == SPI_MEM_DATA_OUT, op->data.nbytes);
1621f8811a2SJonathan Neuschäfer 
1631f8811a2SJonathan Neuschäfer 	if (op->data.dir == SPI_MEM_DATA_IN)
1641f8811a2SJonathan Neuschäfer 		wpcm_fiu_get_data(fiu, op->data.buf.in, op->data.nbytes);
1651f8811a2SJonathan Neuschäfer 
1661f8811a2SJonathan Neuschäfer 	return ret;
1671f8811a2SJonathan Neuschäfer }
1681f8811a2SJonathan Neuschäfer 
wpcm_fiu_fast_read_match(const struct spi_mem_op * op)1691f8811a2SJonathan Neuschäfer static bool wpcm_fiu_fast_read_match(const struct spi_mem_op *op)
1701f8811a2SJonathan Neuschäfer {
1711f8811a2SJonathan Neuschäfer 	return op->cmd.opcode == 0x0b && op->addr.nbytes == 3 &&
1721f8811a2SJonathan Neuschäfer 	       op->dummy.nbytes == 1 &&
1731f8811a2SJonathan Neuschäfer 	       op->data.nbytes >= 1 && op->data.nbytes <= 4 &&
1741f8811a2SJonathan Neuschäfer 	       op->data.dir == SPI_MEM_DATA_IN;
1751f8811a2SJonathan Neuschäfer }
1761f8811a2SJonathan Neuschäfer 
wpcm_fiu_fast_read_exec(struct spi_mem * mem,const struct spi_mem_op * op)1771f8811a2SJonathan Neuschäfer static int wpcm_fiu_fast_read_exec(struct spi_mem *mem, const struct spi_mem_op *op)
1781f8811a2SJonathan Neuschäfer {
1791f8811a2SJonathan Neuschäfer 	return -EINVAL;
1801f8811a2SJonathan Neuschäfer }
1811f8811a2SJonathan Neuschäfer 
1821f8811a2SJonathan Neuschäfer /*
1831f8811a2SJonathan Neuschäfer  * 4-byte addressing.
1841f8811a2SJonathan Neuschäfer  *
1851f8811a2SJonathan Neuschäfer  * Flash view:  [ C  A  A  A   A     D  D  D  D]
1861f8811a2SJonathan Neuschäfer  * bytes:        13 aa bb cc  dd -> 5a a5 f0 0f
1871f8811a2SJonathan Neuschäfer  * FIU's view:  [ C  A  A  A][ C     D  D  D  D]
1881f8811a2SJonathan Neuschäfer  * FIU mode:    [ read/write][      read       ]
1891f8811a2SJonathan Neuschäfer  */
wpcm_fiu_4ba_match(const struct spi_mem_op * op)1901f8811a2SJonathan Neuschäfer static bool wpcm_fiu_4ba_match(const struct spi_mem_op *op)
1911f8811a2SJonathan Neuschäfer {
1921f8811a2SJonathan Neuschäfer 	return op->addr.nbytes == 4 && op->dummy.nbytes == 0 && op->data.nbytes <= 4;
1931f8811a2SJonathan Neuschäfer }
1941f8811a2SJonathan Neuschäfer 
wpcm_fiu_4ba_exec(struct spi_mem * mem,const struct spi_mem_op * op)1951f8811a2SJonathan Neuschäfer static int wpcm_fiu_4ba_exec(struct spi_mem *mem, const struct spi_mem_op *op)
1961f8811a2SJonathan Neuschäfer {
1971f8811a2SJonathan Neuschäfer 	struct wpcm_fiu_spi *fiu = spi_controller_get_devdata(mem->spi->controller);
1989e264f3fSAmit Kumar Mahapatra via Alsa-devel 	int cs = spi_get_chipselect(mem->spi, 0);
1991f8811a2SJonathan Neuschäfer 
2001f8811a2SJonathan Neuschäfer 	wpcm_fiu_ects_assert(fiu, cs);
2011f8811a2SJonathan Neuschäfer 
2021f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_opcode(fiu, op->cmd.opcode);
2031f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_addr(fiu, op->addr.val >> 8);
2041f8811a2SJonathan Neuschäfer 	wpcm_fiu_do_uma(fiu, cs, true, false, 0);
2051f8811a2SJonathan Neuschäfer 
2061f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_opcode(fiu, op->addr.val & 0xff);
2071f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_addr(fiu, 0);
2081f8811a2SJonathan Neuschäfer 	if (op->data.dir == SPI_MEM_DATA_OUT)
2091f8811a2SJonathan Neuschäfer 		wpcm_fiu_set_data(fiu, op->data.buf.out, op->data.nbytes);
2101f8811a2SJonathan Neuschäfer 	wpcm_fiu_do_uma(fiu, cs, false, op->data.dir == SPI_MEM_DATA_OUT, op->data.nbytes);
2111f8811a2SJonathan Neuschäfer 
2121f8811a2SJonathan Neuschäfer 	wpcm_fiu_ects_deassert(fiu, cs);
2131f8811a2SJonathan Neuschäfer 
2141f8811a2SJonathan Neuschäfer 	if (op->data.dir == SPI_MEM_DATA_IN)
2151f8811a2SJonathan Neuschäfer 		wpcm_fiu_get_data(fiu, op->data.buf.in, op->data.nbytes);
2161f8811a2SJonathan Neuschäfer 
2171f8811a2SJonathan Neuschäfer 	return 0;
2181f8811a2SJonathan Neuschäfer }
2191f8811a2SJonathan Neuschäfer 
2201f8811a2SJonathan Neuschäfer /*
2211f8811a2SJonathan Neuschäfer  * RDID (Read Identification) needs special handling because Linux expects to
2221f8811a2SJonathan Neuschäfer  * be able to read 6 ID bytes and FIU can only read up to 4 at once.
2231f8811a2SJonathan Neuschäfer  *
2241f8811a2SJonathan Neuschäfer  * We're lucky in this case, because executing the RDID instruction twice will
2251f8811a2SJonathan Neuschäfer  * result in the same result.
2261f8811a2SJonathan Neuschäfer  *
2271f8811a2SJonathan Neuschäfer  * What we do is as follows (C: write command/opcode byte, D: read data byte,
2281f8811a2SJonathan Neuschäfer  * A: write address byte):
2291f8811a2SJonathan Neuschäfer  *
2301f8811a2SJonathan Neuschäfer  *  1. C D D D
2311f8811a2SJonathan Neuschäfer  *  2. C A A A D D D
2321f8811a2SJonathan Neuschäfer  */
wpcm_fiu_rdid_match(const struct spi_mem_op * op)2331f8811a2SJonathan Neuschäfer static bool wpcm_fiu_rdid_match(const struct spi_mem_op *op)
2341f8811a2SJonathan Neuschäfer {
2351f8811a2SJonathan Neuschäfer 	return op->cmd.opcode == 0x9f && op->addr.nbytes == 0 &&
2361f8811a2SJonathan Neuschäfer 	       op->dummy.nbytes == 0 && op->data.nbytes == 6 &&
2371f8811a2SJonathan Neuschäfer 	       op->data.dir == SPI_MEM_DATA_IN;
2381f8811a2SJonathan Neuschäfer }
2391f8811a2SJonathan Neuschäfer 
wpcm_fiu_rdid_exec(struct spi_mem * mem,const struct spi_mem_op * op)2401f8811a2SJonathan Neuschäfer static int wpcm_fiu_rdid_exec(struct spi_mem *mem, const struct spi_mem_op *op)
2411f8811a2SJonathan Neuschäfer {
2421f8811a2SJonathan Neuschäfer 	struct wpcm_fiu_spi *fiu = spi_controller_get_devdata(mem->spi->controller);
2439e264f3fSAmit Kumar Mahapatra via Alsa-devel 	int cs = spi_get_chipselect(mem->spi, 0);
2441f8811a2SJonathan Neuschäfer 
2451f8811a2SJonathan Neuschäfer 	/* First transfer */
2461f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_opcode(fiu, op->cmd.opcode);
2471f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_addr(fiu, 0);
2481f8811a2SJonathan Neuschäfer 	wpcm_fiu_do_uma(fiu, cs, false, false, 3);
2491f8811a2SJonathan Neuschäfer 	wpcm_fiu_get_data(fiu, op->data.buf.in, 3);
2501f8811a2SJonathan Neuschäfer 
2511f8811a2SJonathan Neuschäfer 	/* Second transfer */
2521f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_opcode(fiu, op->cmd.opcode);
2531f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_addr(fiu, 0);
2541f8811a2SJonathan Neuschäfer 	wpcm_fiu_do_uma(fiu, cs, true, false, 3);
2551f8811a2SJonathan Neuschäfer 	wpcm_fiu_get_data(fiu, op->data.buf.in + 3, 3);
2561f8811a2SJonathan Neuschäfer 
2571f8811a2SJonathan Neuschäfer 	return 0;
2581f8811a2SJonathan Neuschäfer }
2591f8811a2SJonathan Neuschäfer 
2601f8811a2SJonathan Neuschäfer /*
2611f8811a2SJonathan Neuschäfer  * With some dummy bytes.
2621f8811a2SJonathan Neuschäfer  *
2631f8811a2SJonathan Neuschäfer  *  C A A A  X*  X D D D D
2641f8811a2SJonathan Neuschäfer  * [C A A A  D*][C D D D D]
2651f8811a2SJonathan Neuschäfer  */
wpcm_fiu_dummy_match(const struct spi_mem_op * op)2661f8811a2SJonathan Neuschäfer static bool wpcm_fiu_dummy_match(const struct spi_mem_op *op)
2671f8811a2SJonathan Neuschäfer {
2681f8811a2SJonathan Neuschäfer 	// Opcode 0x0b (FAST READ) is treated differently in hardware
2691f8811a2SJonathan Neuschäfer 	if (op->cmd.opcode == 0x0b)
2701f8811a2SJonathan Neuschäfer 		return false;
2711f8811a2SJonathan Neuschäfer 
2721f8811a2SJonathan Neuschäfer 	return (op->addr.nbytes == 0 || op->addr.nbytes == 3) &&
2731f8811a2SJonathan Neuschäfer 	       op->dummy.nbytes >= 1 && op->dummy.nbytes <= 5 &&
2741f8811a2SJonathan Neuschäfer 	       op->data.nbytes <= 4;
2751f8811a2SJonathan Neuschäfer }
2761f8811a2SJonathan Neuschäfer 
wpcm_fiu_dummy_exec(struct spi_mem * mem,const struct spi_mem_op * op)2771f8811a2SJonathan Neuschäfer static int wpcm_fiu_dummy_exec(struct spi_mem *mem, const struct spi_mem_op *op)
2781f8811a2SJonathan Neuschäfer {
2791f8811a2SJonathan Neuschäfer 	struct wpcm_fiu_spi *fiu = spi_controller_get_devdata(mem->spi->controller);
2809e264f3fSAmit Kumar Mahapatra via Alsa-devel 	int cs = spi_get_chipselect(mem->spi, 0);
2811f8811a2SJonathan Neuschäfer 
2821f8811a2SJonathan Neuschäfer 	wpcm_fiu_ects_assert(fiu, cs);
2831f8811a2SJonathan Neuschäfer 
2841f8811a2SJonathan Neuschäfer 	/* First transfer */
2851f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_opcode(fiu, op->cmd.opcode);
2861f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_addr(fiu, op->addr.val);
2871f8811a2SJonathan Neuschäfer 	wpcm_fiu_do_uma(fiu, cs, op->addr.nbytes != 0, true, op->dummy.nbytes - 1);
2881f8811a2SJonathan Neuschäfer 
2891f8811a2SJonathan Neuschäfer 	/* Second transfer */
2901f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_opcode(fiu, 0);
2911f8811a2SJonathan Neuschäfer 	wpcm_fiu_set_addr(fiu, 0);
2921f8811a2SJonathan Neuschäfer 	wpcm_fiu_do_uma(fiu, cs, false, false, op->data.nbytes);
2931f8811a2SJonathan Neuschäfer 	wpcm_fiu_get_data(fiu, op->data.buf.in, op->data.nbytes);
2941f8811a2SJonathan Neuschäfer 
2951f8811a2SJonathan Neuschäfer 	wpcm_fiu_ects_deassert(fiu, cs);
2961f8811a2SJonathan Neuschäfer 
2971f8811a2SJonathan Neuschäfer 	return 0;
2981f8811a2SJonathan Neuschäfer }
2991f8811a2SJonathan Neuschäfer 
3001f8811a2SJonathan Neuschäfer static const struct wpcm_fiu_op_shape wpcm_fiu_op_shapes[] = {
3011f8811a2SJonathan Neuschäfer 	{ .match = wpcm_fiu_normal_match, .exec = wpcm_fiu_normal_exec },
3021f8811a2SJonathan Neuschäfer 	{ .match = wpcm_fiu_fast_read_match, .exec = wpcm_fiu_fast_read_exec },
3031f8811a2SJonathan Neuschäfer 	{ .match = wpcm_fiu_4ba_match, .exec = wpcm_fiu_4ba_exec },
3041f8811a2SJonathan Neuschäfer 	{ .match = wpcm_fiu_rdid_match, .exec = wpcm_fiu_rdid_exec },
3051f8811a2SJonathan Neuschäfer 	{ .match = wpcm_fiu_dummy_match, .exec = wpcm_fiu_dummy_exec },
3061f8811a2SJonathan Neuschäfer };
3071f8811a2SJonathan Neuschäfer 
wpcm_fiu_find_op_shape(const struct spi_mem_op * op)3081f8811a2SJonathan Neuschäfer static const struct wpcm_fiu_op_shape *wpcm_fiu_find_op_shape(const struct spi_mem_op *op)
3091f8811a2SJonathan Neuschäfer {
3101f8811a2SJonathan Neuschäfer 	size_t i;
3111f8811a2SJonathan Neuschäfer 
3121f8811a2SJonathan Neuschäfer 	for (i = 0; i < ARRAY_SIZE(wpcm_fiu_op_shapes); i++) {
3131f8811a2SJonathan Neuschäfer 		const struct wpcm_fiu_op_shape *shape = &wpcm_fiu_op_shapes[i];
3141f8811a2SJonathan Neuschäfer 
3151f8811a2SJonathan Neuschäfer 		if (shape->match(op))
3161f8811a2SJonathan Neuschäfer 			return shape;
3171f8811a2SJonathan Neuschäfer 	}
3181f8811a2SJonathan Neuschäfer 
3191f8811a2SJonathan Neuschäfer 	return NULL;
3201f8811a2SJonathan Neuschäfer }
3211f8811a2SJonathan Neuschäfer 
wpcm_fiu_supports_op(struct spi_mem * mem,const struct spi_mem_op * op)3221f8811a2SJonathan Neuschäfer static bool wpcm_fiu_supports_op(struct spi_mem *mem, const struct spi_mem_op *op)
3231f8811a2SJonathan Neuschäfer {
3241f8811a2SJonathan Neuschäfer 	if (!spi_mem_default_supports_op(mem, op))
3251f8811a2SJonathan Neuschäfer 		return false;
3261f8811a2SJonathan Neuschäfer 
3271f8811a2SJonathan Neuschäfer 	if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr)
3281f8811a2SJonathan Neuschäfer 		return false;
3291f8811a2SJonathan Neuschäfer 
3301f8811a2SJonathan Neuschäfer 	if (op->cmd.buswidth > 1 || op->addr.buswidth > 1 ||
3311f8811a2SJonathan Neuschäfer 	    op->dummy.buswidth > 1 || op->data.buswidth > 1)
3321f8811a2SJonathan Neuschäfer 		return false;
3331f8811a2SJonathan Neuschäfer 
3341f8811a2SJonathan Neuschäfer 	return wpcm_fiu_find_op_shape(op) != NULL;
3351f8811a2SJonathan Neuschäfer }
3361f8811a2SJonathan Neuschäfer 
3371f8811a2SJonathan Neuschäfer /*
3381f8811a2SJonathan Neuschäfer  * In order to ensure the integrity of SPI transfers performed via UMA,
3391f8811a2SJonathan Neuschäfer  * temporarily disable (stall) memory accesses coming from the host CPU.
3401f8811a2SJonathan Neuschäfer  */
wpcm_fiu_stall_host(struct wpcm_fiu_spi * fiu,bool stall)3411f8811a2SJonathan Neuschäfer static void wpcm_fiu_stall_host(struct wpcm_fiu_spi *fiu, bool stall)
3421f8811a2SJonathan Neuschäfer {
3431f8811a2SJonathan Neuschäfer 	if (fiu->shm_regmap) {
3441f8811a2SJonathan Neuschäfer 		int res = regmap_update_bits(fiu->shm_regmap, SHM_FLASH_SIZE,
3451f8811a2SJonathan Neuschäfer 					     SHM_FLASH_SIZE_STALL_HOST,
3461f8811a2SJonathan Neuschäfer 					     stall ? SHM_FLASH_SIZE_STALL_HOST : 0);
3471f8811a2SJonathan Neuschäfer 		if (res)
3481f8811a2SJonathan Neuschäfer 			dev_warn(fiu->dev, "Failed to (un)stall host memory accesses: %d\n", res);
3491f8811a2SJonathan Neuschäfer 	}
3501f8811a2SJonathan Neuschäfer }
3511f8811a2SJonathan Neuschäfer 
wpcm_fiu_exec_op(struct spi_mem * mem,const struct spi_mem_op * op)3521f8811a2SJonathan Neuschäfer static int wpcm_fiu_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
3531f8811a2SJonathan Neuschäfer {
3541f8811a2SJonathan Neuschäfer 	struct wpcm_fiu_spi *fiu = spi_controller_get_devdata(mem->spi->controller);
3551f8811a2SJonathan Neuschäfer 	const struct wpcm_fiu_op_shape *shape = wpcm_fiu_find_op_shape(op);
3561f8811a2SJonathan Neuschäfer 
3571f8811a2SJonathan Neuschäfer 	wpcm_fiu_stall_host(fiu, true);
3581f8811a2SJonathan Neuschäfer 
3591f8811a2SJonathan Neuschäfer 	if (shape)
3601f8811a2SJonathan Neuschäfer 		return shape->exec(mem, op);
3611f8811a2SJonathan Neuschäfer 
3621f8811a2SJonathan Neuschäfer 	wpcm_fiu_stall_host(fiu, false);
3631f8811a2SJonathan Neuschäfer 
3641f8811a2SJonathan Neuschäfer 	return -ENOTSUPP;
3651f8811a2SJonathan Neuschäfer }
3661f8811a2SJonathan Neuschäfer 
wpcm_fiu_adjust_op_size(struct spi_mem * mem,struct spi_mem_op * op)3671f8811a2SJonathan Neuschäfer static int wpcm_fiu_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
3681f8811a2SJonathan Neuschäfer {
3691f8811a2SJonathan Neuschäfer 	if (op->data.nbytes > 4)
3701f8811a2SJonathan Neuschäfer 		op->data.nbytes = 4;
3711f8811a2SJonathan Neuschäfer 
3721f8811a2SJonathan Neuschäfer 	return 0;
3731f8811a2SJonathan Neuschäfer }
3741f8811a2SJonathan Neuschäfer 
wpcm_fiu_dirmap_create(struct spi_mem_dirmap_desc * desc)3759838c182SJonathan Neuschäfer static int wpcm_fiu_dirmap_create(struct spi_mem_dirmap_desc *desc)
3769838c182SJonathan Neuschäfer {
3779838c182SJonathan Neuschäfer 	struct wpcm_fiu_spi *fiu = spi_controller_get_devdata(desc->mem->spi->controller);
3789e264f3fSAmit Kumar Mahapatra via Alsa-devel 	int cs = spi_get_chipselect(desc->mem->spi, 0);
3799838c182SJonathan Neuschäfer 
3809838c182SJonathan Neuschäfer 	if (desc->info.op_tmpl.data.dir != SPI_MEM_DATA_IN)
3819838c182SJonathan Neuschäfer 		return -ENOTSUPP;
3829838c182SJonathan Neuschäfer 
3839838c182SJonathan Neuschäfer 	/*
3849838c182SJonathan Neuschäfer 	 * Unfortunately, FIU only supports a 16 MiB direct mapping window (per
3859838c182SJonathan Neuschäfer 	 * attached flash chip), but the SPI MEM core doesn't support partial
3869838c182SJonathan Neuschäfer 	 * direct mappings. This means that we can't support direct mapping on
3879838c182SJonathan Neuschäfer 	 * flashes that are bigger than 16 MiB.
3889838c182SJonathan Neuschäfer 	 */
3899838c182SJonathan Neuschäfer 	if (desc->info.offset + desc->info.length > MAX_MEMORY_SIZE_PER_CS)
3909838c182SJonathan Neuschäfer 		return -ENOTSUPP;
3919838c182SJonathan Neuschäfer 
3929838c182SJonathan Neuschäfer 	/* Don't read past the memory window */
3939838c182SJonathan Neuschäfer 	if (cs * MAX_MEMORY_SIZE_PER_CS + desc->info.offset + desc->info.length > fiu->memory_size)
3949838c182SJonathan Neuschäfer 		return -ENOTSUPP;
3959838c182SJonathan Neuschäfer 
3969838c182SJonathan Neuschäfer 	return 0;
3979838c182SJonathan Neuschäfer }
3989838c182SJonathan Neuschäfer 
wpcm_fiu_direct_read(struct spi_mem_dirmap_desc * desc,u64 offs,size_t len,void * buf)3999838c182SJonathan Neuschäfer static ssize_t wpcm_fiu_direct_read(struct spi_mem_dirmap_desc *desc, u64 offs, size_t len, void *buf)
4009838c182SJonathan Neuschäfer {
4019838c182SJonathan Neuschäfer 	struct wpcm_fiu_spi *fiu = spi_controller_get_devdata(desc->mem->spi->controller);
4029e264f3fSAmit Kumar Mahapatra via Alsa-devel 	int cs = spi_get_chipselect(desc->mem->spi, 0);
4039838c182SJonathan Neuschäfer 
4049838c182SJonathan Neuschäfer 	if (offs >= MAX_MEMORY_SIZE_PER_CS)
4059838c182SJonathan Neuschäfer 		return -ENOTSUPP;
4069838c182SJonathan Neuschäfer 
4079838c182SJonathan Neuschäfer 	offs += cs * MAX_MEMORY_SIZE_PER_CS;
4089838c182SJonathan Neuschäfer 
4099838c182SJonathan Neuschäfer 	if (!fiu->memory || offs >= fiu->memory_size)
4109838c182SJonathan Neuschäfer 		return -ENOTSUPP;
4119838c182SJonathan Neuschäfer 
4129838c182SJonathan Neuschäfer 	len = min_t(size_t, len, fiu->memory_size - offs);
4139838c182SJonathan Neuschäfer 	memcpy_fromio(buf, fiu->memory + offs, len);
4149838c182SJonathan Neuschäfer 
4159838c182SJonathan Neuschäfer 	return len;
4169838c182SJonathan Neuschäfer }
4179838c182SJonathan Neuschäfer 
4181f8811a2SJonathan Neuschäfer static const struct spi_controller_mem_ops wpcm_fiu_mem_ops = {
4191f8811a2SJonathan Neuschäfer 	.adjust_op_size = wpcm_fiu_adjust_op_size,
4201f8811a2SJonathan Neuschäfer 	.supports_op = wpcm_fiu_supports_op,
4211f8811a2SJonathan Neuschäfer 	.exec_op = wpcm_fiu_exec_op,
4229838c182SJonathan Neuschäfer 	.dirmap_create = wpcm_fiu_dirmap_create,
4239838c182SJonathan Neuschäfer 	.dirmap_read = wpcm_fiu_direct_read,
4241f8811a2SJonathan Neuschäfer };
4251f8811a2SJonathan Neuschäfer 
wpcm_fiu_hw_init(struct wpcm_fiu_spi * fiu)4261f8811a2SJonathan Neuschäfer static void wpcm_fiu_hw_init(struct wpcm_fiu_spi *fiu)
4271f8811a2SJonathan Neuschäfer {
4289838c182SJonathan Neuschäfer 	/* Configure memory-mapped flash access */
4299838c182SJonathan Neuschäfer 	writeb(FIU_BURST_CFG_R16, fiu->regs + FIU_BURST_BFG);
4309838c182SJonathan Neuschäfer 	writeb(MAX_MEMORY_SIZE_TOTAL / (512 << 10), fiu->regs + FIU_CFG);
4319838c182SJonathan Neuschäfer 	writeb(MAX_MEMORY_SIZE_PER_CS / (512 << 10) | BIT(6), fiu->regs + FIU_SPI_FL_CFG);
4329838c182SJonathan Neuschäfer 
4331f8811a2SJonathan Neuschäfer 	/* Deassert all manually asserted chip selects */
4341f8811a2SJonathan Neuschäfer 	writeb(0x0f, fiu->regs + FIU_UMA_ECTS);
4351f8811a2SJonathan Neuschäfer }
4361f8811a2SJonathan Neuschäfer 
wpcm_fiu_probe(struct platform_device * pdev)4371f8811a2SJonathan Neuschäfer static int wpcm_fiu_probe(struct platform_device *pdev)
4381f8811a2SJonathan Neuschäfer {
4391f8811a2SJonathan Neuschäfer 	struct device *dev = &pdev->dev;
4401f8811a2SJonathan Neuschäfer 	struct spi_controller *ctrl;
4411f8811a2SJonathan Neuschäfer 	struct wpcm_fiu_spi *fiu;
4421f8811a2SJonathan Neuschäfer 	struct resource *res;
4431f8811a2SJonathan Neuschäfer 
4441f8811a2SJonathan Neuschäfer 	ctrl = devm_spi_alloc_master(dev, sizeof(*fiu));
4451f8811a2SJonathan Neuschäfer 	if (!ctrl)
4461f8811a2SJonathan Neuschäfer 		return -ENOMEM;
4471f8811a2SJonathan Neuschäfer 
4481f8811a2SJonathan Neuschäfer 	fiu = spi_controller_get_devdata(ctrl);
4491f8811a2SJonathan Neuschäfer 	fiu->dev = dev;
4501f8811a2SJonathan Neuschäfer 
4511f8811a2SJonathan Neuschäfer 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "control");
4521f8811a2SJonathan Neuschäfer 	fiu->regs = devm_ioremap_resource(dev, res);
4531f8811a2SJonathan Neuschäfer 	if (IS_ERR(fiu->regs)) {
4541f8811a2SJonathan Neuschäfer 		dev_err(dev, "Failed to map registers\n");
4551f8811a2SJonathan Neuschäfer 		return PTR_ERR(fiu->regs);
4561f8811a2SJonathan Neuschäfer 	}
4571f8811a2SJonathan Neuschäfer 
4581f8811a2SJonathan Neuschäfer 	fiu->clk = devm_clk_get_enabled(dev, NULL);
4591f8811a2SJonathan Neuschäfer 	if (IS_ERR(fiu->clk))
4601f8811a2SJonathan Neuschäfer 		return PTR_ERR(fiu->clk);
4611f8811a2SJonathan Neuschäfer 
4629838c182SJonathan Neuschäfer 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "memory");
4639838c182SJonathan Neuschäfer 	fiu->memory = devm_ioremap_resource(dev, res);
4649838c182SJonathan Neuschäfer 	fiu->memory_size = min_t(size_t, resource_size(res), MAX_MEMORY_SIZE_TOTAL);
4659838c182SJonathan Neuschäfer 	if (IS_ERR(fiu->memory)) {
4669838c182SJonathan Neuschäfer 		dev_err(dev, "Failed to map flash memory window\n");
4679838c182SJonathan Neuschäfer 		return PTR_ERR(fiu->memory);
4689838c182SJonathan Neuschäfer 	}
4699838c182SJonathan Neuschäfer 
4701f8811a2SJonathan Neuschäfer 	fiu->shm_regmap = syscon_regmap_lookup_by_phandle_optional(dev->of_node, "nuvoton,shm");
4711f8811a2SJonathan Neuschäfer 
4721f8811a2SJonathan Neuschäfer 	wpcm_fiu_hw_init(fiu);
4731f8811a2SJonathan Neuschäfer 
4741f8811a2SJonathan Neuschäfer 	ctrl->bus_num = -1;
4751f8811a2SJonathan Neuschäfer 	ctrl->mem_ops = &wpcm_fiu_mem_ops;
4761f8811a2SJonathan Neuschäfer 	ctrl->num_chipselect = 4;
4771f8811a2SJonathan Neuschäfer 	ctrl->dev.of_node = dev->of_node;
4781f8811a2SJonathan Neuschäfer 
4791f8811a2SJonathan Neuschäfer 	/*
4801f8811a2SJonathan Neuschäfer 	 * The FIU doesn't include a clock divider, the clock is entirely
4811f8811a2SJonathan Neuschäfer 	 * determined by the AHB3 bus clock.
4821f8811a2SJonathan Neuschäfer 	 */
4831f8811a2SJonathan Neuschäfer 	ctrl->min_speed_hz = clk_get_rate(fiu->clk);
4841f8811a2SJonathan Neuschäfer 	ctrl->max_speed_hz = clk_get_rate(fiu->clk);
4851f8811a2SJonathan Neuschäfer 
4861f8811a2SJonathan Neuschäfer 	return devm_spi_register_controller(dev, ctrl);
4871f8811a2SJonathan Neuschäfer }
4881f8811a2SJonathan Neuschäfer 
4891f8811a2SJonathan Neuschäfer static const struct of_device_id wpcm_fiu_dt_ids[] = {
4901f8811a2SJonathan Neuschäfer 	{ .compatible = "nuvoton,wpcm450-fiu", },
4911f8811a2SJonathan Neuschäfer 	{ }
4921f8811a2SJonathan Neuschäfer };
4931f8811a2SJonathan Neuschäfer MODULE_DEVICE_TABLE(of, wpcm_fiu_dt_ids);
4941f8811a2SJonathan Neuschäfer 
4951f8811a2SJonathan Neuschäfer static struct platform_driver wpcm_fiu_driver = {
4961f8811a2SJonathan Neuschäfer 	.driver = {
4971f8811a2SJonathan Neuschäfer 		.name	= "wpcm450-fiu",
4981f8811a2SJonathan Neuschäfer 		.bus	= &platform_bus_type,
4991f8811a2SJonathan Neuschäfer 		.of_match_table = wpcm_fiu_dt_ids,
5001f8811a2SJonathan Neuschäfer 	},
5011f8811a2SJonathan Neuschäfer 	.probe      = wpcm_fiu_probe,
5021f8811a2SJonathan Neuschäfer };
5031f8811a2SJonathan Neuschäfer module_platform_driver(wpcm_fiu_driver);
5041f8811a2SJonathan Neuschäfer 
5051f8811a2SJonathan Neuschäfer MODULE_DESCRIPTION("Nuvoton WPCM450 FIU SPI controller driver");
5061f8811a2SJonathan Neuschäfer MODULE_AUTHOR("Jonathan Neuschäfer <j.neuschaefer@gmx.net>");
5071f8811a2SJonathan Neuschäfer MODULE_LICENSE("GPL");
508