| // 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; | |
| 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"); |