|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | /* Copyright (C) 2022 Hewlett-Packard Development Company, L.P. */ | 
|  |  | 
|  | #include <linux/iopoll.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/spi/spi.h> | 
|  | #include <linux/spi/spi-mem.h> | 
|  |  | 
|  | #define GXP_SPI0_MAX_CHIPSELECT	2 | 
|  | #define GXP_SPI_SLEEP_TIME	1 | 
|  | #define GXP_SPI_TIMEOUT (130 * 1000000 / GXP_SPI_SLEEP_TIME) | 
|  |  | 
|  | #define MANUAL_MODE		0 | 
|  | #define DIRECT_MODE		1 | 
|  | #define SPILDAT_LEN		256 | 
|  |  | 
|  | #define OFFSET_SPIMCFG		0x0 | 
|  | #define OFFSET_SPIMCTRL		0x4 | 
|  | #define OFFSET_SPICMD		0x5 | 
|  | #define OFFSET_SPIDCNT		0x6 | 
|  | #define OFFSET_SPIADDR		0x8 | 
|  | #define OFFSET_SPIINTSTS	0xc | 
|  |  | 
|  | #define SPIMCTRL_START		0x01 | 
|  | #define SPIMCTRL_BUSY		0x02 | 
|  | #define SPIMCTRL_DIR		0x08 | 
|  |  | 
|  | struct gxp_spi; | 
|  |  | 
|  | struct gxp_spi_chip { | 
|  | struct gxp_spi *spifi; | 
|  | u32 cs; | 
|  | }; | 
|  |  | 
|  | struct gxp_spi_data { | 
|  | u32 max_cs; | 
|  | u32 mode_bits; | 
|  | }; | 
|  |  | 
|  | struct gxp_spi { | 
|  | const struct gxp_spi_data *data; | 
|  | void __iomem *reg_base; | 
|  | void __iomem *dat_base; | 
|  | void __iomem *dir_base; | 
|  | struct device *dev; | 
|  | struct gxp_spi_chip chips[GXP_SPI0_MAX_CHIPSELECT]; | 
|  | }; | 
|  |  | 
|  | static void gxp_spi_set_mode(struct gxp_spi *spifi, int mode) | 
|  | { | 
|  | u8 value; | 
|  | void __iomem *reg_base = spifi->reg_base; | 
|  |  | 
|  | value = readb(reg_base + OFFSET_SPIMCTRL); | 
|  |  | 
|  | if (mode == MANUAL_MODE) { | 
|  | writeb(0x55, reg_base + OFFSET_SPICMD); | 
|  | writeb(0xaa, reg_base + OFFSET_SPICMD); | 
|  | value &= ~0x30; | 
|  | } else { | 
|  | value |= 0x30; | 
|  | } | 
|  | writeb(value, reg_base + OFFSET_SPIMCTRL); | 
|  | } | 
|  |  | 
|  | static int gxp_spi_read_reg(struct gxp_spi_chip *chip, const struct spi_mem_op *op) | 
|  | { | 
|  | int ret; | 
|  | struct gxp_spi *spifi = chip->spifi; | 
|  | void __iomem *reg_base = spifi->reg_base; | 
|  | u32 value; | 
|  |  | 
|  | value = readl(reg_base + OFFSET_SPIMCFG); | 
|  | value &= ~(1 << 24); | 
|  | value |= (chip->cs << 24); | 
|  | value &= ~(0x07 << 16); | 
|  | value &= ~(0x1f << 19); | 
|  | writel(value, reg_base + OFFSET_SPIMCFG); | 
|  |  | 
|  | writel(0, reg_base + OFFSET_SPIADDR); | 
|  |  | 
|  | writeb(op->cmd.opcode, reg_base + OFFSET_SPICMD); | 
|  |  | 
|  | writew(op->data.nbytes, reg_base + OFFSET_SPIDCNT); | 
|  |  | 
|  | value = readb(reg_base + OFFSET_SPIMCTRL); | 
|  | value &= ~SPIMCTRL_DIR; | 
|  | value |= SPIMCTRL_START; | 
|  |  | 
|  | writeb(value, reg_base + OFFSET_SPIMCTRL); | 
|  |  | 
|  | ret = readb_poll_timeout(reg_base + OFFSET_SPIMCTRL, value, | 
|  | !(value & SPIMCTRL_BUSY), | 
|  | GXP_SPI_SLEEP_TIME, GXP_SPI_TIMEOUT); | 
|  | if (ret) { | 
|  | dev_warn(spifi->dev, "read reg busy time out\n"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | memcpy_fromio(op->data.buf.in, spifi->dat_base, op->data.nbytes); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int gxp_spi_write_reg(struct gxp_spi_chip *chip, const struct spi_mem_op *op) | 
|  | { | 
|  | int ret; | 
|  | struct gxp_spi *spifi = chip->spifi; | 
|  | void __iomem *reg_base = spifi->reg_base; | 
|  | u32 value; | 
|  |  | 
|  | value = readl(reg_base + OFFSET_SPIMCFG); | 
|  | value &= ~(1 << 24); | 
|  | value |= (chip->cs << 24); | 
|  | value &= ~(0x07 << 16); | 
|  | value &= ~(0x1f << 19); | 
|  | writel(value, reg_base + OFFSET_SPIMCFG); | 
|  |  | 
|  | writel(0, reg_base + OFFSET_SPIADDR); | 
|  |  | 
|  | writeb(op->cmd.opcode, reg_base + OFFSET_SPICMD); | 
|  |  | 
|  | memcpy_toio(spifi->dat_base, op->data.buf.in, op->data.nbytes); | 
|  |  | 
|  | writew(op->data.nbytes, reg_base + OFFSET_SPIDCNT); | 
|  |  | 
|  | value = readb(reg_base + OFFSET_SPIMCTRL); | 
|  | value |= SPIMCTRL_DIR; | 
|  | value |= SPIMCTRL_START; | 
|  |  | 
|  | writeb(value, reg_base + OFFSET_SPIMCTRL); | 
|  |  | 
|  | ret = readb_poll_timeout(reg_base + OFFSET_SPIMCTRL, value, | 
|  | !(value & SPIMCTRL_BUSY), | 
|  | GXP_SPI_SLEEP_TIME, GXP_SPI_TIMEOUT); | 
|  | if (ret) | 
|  | dev_warn(spifi->dev, "write reg busy time out\n"); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static ssize_t gxp_spi_read(struct gxp_spi_chip *chip, const struct spi_mem_op *op) | 
|  | { | 
|  | struct gxp_spi *spifi = chip->spifi; | 
|  | u32 offset = op->addr.val; | 
|  |  | 
|  | if (chip->cs == 0) | 
|  | offset += 0x4000000; | 
|  |  | 
|  | memcpy_fromio(op->data.buf.in, spifi->dir_base + offset, op->data.nbytes); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static ssize_t gxp_spi_write(struct gxp_spi_chip *chip, const struct spi_mem_op *op) | 
|  | { | 
|  | struct gxp_spi *spifi = chip->spifi; | 
|  | void __iomem *reg_base = spifi->reg_base; | 
|  | u32 write_len; | 
|  | u32 value; | 
|  | int ret; | 
|  |  | 
|  | write_len = op->data.nbytes; | 
|  | if (write_len > SPILDAT_LEN) | 
|  | write_len = SPILDAT_LEN; | 
|  |  | 
|  | value = readl(reg_base + OFFSET_SPIMCFG); | 
|  | value &= ~(1 << 24); | 
|  | value |= (chip->cs << 24); | 
|  | value &= ~(0x07 << 16); | 
|  | value |= (op->addr.nbytes << 16); | 
|  | value &= ~(0x1f << 19); | 
|  | writel(value, reg_base + OFFSET_SPIMCFG); | 
|  |  | 
|  | writel(op->addr.val, reg_base + OFFSET_SPIADDR); | 
|  |  | 
|  | writeb(op->cmd.opcode, reg_base + OFFSET_SPICMD); | 
|  |  | 
|  | writew(write_len, reg_base + OFFSET_SPIDCNT); | 
|  |  | 
|  | memcpy_toio(spifi->dat_base, op->data.buf.in, write_len); | 
|  |  | 
|  | value = readb(reg_base + OFFSET_SPIMCTRL); | 
|  | value |= SPIMCTRL_DIR; | 
|  | value |= SPIMCTRL_START; | 
|  |  | 
|  | writeb(value, reg_base + OFFSET_SPIMCTRL); | 
|  |  | 
|  | ret = readb_poll_timeout(reg_base + OFFSET_SPIMCTRL, value, | 
|  | !(value & SPIMCTRL_BUSY), | 
|  | GXP_SPI_SLEEP_TIME, GXP_SPI_TIMEOUT); | 
|  | if (ret) { | 
|  | dev_warn(spifi->dev, "write busy time out\n"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int do_gxp_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) | 
|  | { | 
|  | struct gxp_spi *spifi = spi_controller_get_devdata(mem->spi->controller); | 
|  | struct gxp_spi_chip *chip = &spifi->chips[spi_get_chipselect(mem->spi, 0)]; | 
|  | int ret; | 
|  |  | 
|  | if (op->data.dir == SPI_MEM_DATA_IN) { | 
|  | if (!op->addr.nbytes) | 
|  | ret = gxp_spi_read_reg(chip, op); | 
|  | else | 
|  | ret = gxp_spi_read(chip, op); | 
|  | } else { | 
|  | if (!op->addr.nbytes) | 
|  | ret = gxp_spi_write_reg(chip, op); | 
|  | else | 
|  | ret = gxp_spi_write(chip, op); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int gxp_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = do_gxp_exec_mem_op(mem, op); | 
|  | if (ret) | 
|  | dev_err(&mem->spi->dev, "operation failed: %d", ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static const struct spi_controller_mem_ops gxp_spi_mem_ops = { | 
|  | .exec_op = gxp_exec_mem_op, | 
|  | }; | 
|  |  | 
|  | static int gxp_spi_setup(struct spi_device *spi) | 
|  | { | 
|  | struct gxp_spi *spifi = spi_controller_get_devdata(spi->controller); | 
|  | unsigned int cs = spi_get_chipselect(spi, 0); | 
|  | struct gxp_spi_chip *chip = &spifi->chips[cs]; | 
|  |  | 
|  | chip->spifi = spifi; | 
|  | chip->cs = cs; | 
|  |  | 
|  | gxp_spi_set_mode(spifi, MANUAL_MODE); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int gxp_spifi_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct device *dev = &pdev->dev; | 
|  | const struct gxp_spi_data *data; | 
|  | struct spi_controller *ctlr; | 
|  | struct gxp_spi *spifi; | 
|  | int ret; | 
|  |  | 
|  | data = of_device_get_match_data(&pdev->dev); | 
|  |  | 
|  | ctlr = devm_spi_alloc_host(dev, sizeof(*spifi)); | 
|  | if (!ctlr) | 
|  | return -ENOMEM; | 
|  |  | 
|  | spifi = spi_controller_get_devdata(ctlr); | 
|  |  | 
|  | platform_set_drvdata(pdev, spifi); | 
|  | spifi->data = data; | 
|  | spifi->dev = dev; | 
|  |  | 
|  | spifi->reg_base = devm_platform_ioremap_resource(pdev, 0); | 
|  | if (IS_ERR(spifi->reg_base)) | 
|  | return PTR_ERR(spifi->reg_base); | 
|  |  | 
|  | spifi->dat_base = devm_platform_ioremap_resource(pdev, 1); | 
|  | if (IS_ERR(spifi->dat_base)) | 
|  | return PTR_ERR(spifi->dat_base); | 
|  |  | 
|  | spifi->dir_base = devm_platform_ioremap_resource(pdev, 2); | 
|  | if (IS_ERR(spifi->dir_base)) | 
|  | return PTR_ERR(spifi->dir_base); | 
|  |  | 
|  | ctlr->mode_bits = data->mode_bits; | 
|  | ctlr->bus_num = pdev->id; | 
|  | ctlr->mem_ops = &gxp_spi_mem_ops; | 
|  | ctlr->setup = gxp_spi_setup; | 
|  | ctlr->num_chipselect = data->max_cs; | 
|  | ctlr->dev.of_node = dev->of_node; | 
|  |  | 
|  | ret = devm_spi_register_controller(dev, ctlr); | 
|  | if (ret) { | 
|  | return dev_err_probe(&pdev->dev, ret, | 
|  | "failed to register spi controller\n"); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct gxp_spi_data gxp_spifi_data = { | 
|  | .max_cs	= 2, | 
|  | .mode_bits = 0, | 
|  | }; | 
|  |  | 
|  | static const struct of_device_id gxp_spifi_match[] = { | 
|  | {.compatible = "hpe,gxp-spifi", .data = &gxp_spifi_data }, | 
|  | { /* null */ } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, gxp_spifi_match); | 
|  |  | 
|  | static struct platform_driver gxp_spifi_driver = { | 
|  | .probe = gxp_spifi_probe, | 
|  | .driver = { | 
|  | .name = "gxp-spifi", | 
|  | .of_match_table = gxp_spifi_match, | 
|  | }, | 
|  | }; | 
|  | module_platform_driver(gxp_spifi_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("HPE GXP SPI Flash Interface driver"); | 
|  | MODULE_AUTHOR("Nick Hawkins <nick.hawkins@hpe.com>"); | 
|  | MODULE_LICENSE("GPL"); |