| // 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/of_device.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 write_len; |
| } |
| |
| 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->master); |
| struct gxp_spi_chip *chip = &spifi->chips[mem->spi->chip_select]; |
| 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->master); |
| unsigned int cs = spi->chip_select; |
| 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_master(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"); |