// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2015 Marvell International Ltd. * * Copyright (C) 2016 Stefan Roese */ #include #include #include #include #include #include #include #include #include #include #include DECLARE_GLOBAL_DATA_PTR; #define MVEBU_SPI_A3700_XFER_RDY BIT(1) #define MVEBU_SPI_A3700_FIFO_FLUSH BIT(9) #define MVEBU_SPI_A3700_BYTE_LEN BIT(5) #define MVEBU_SPI_A3700_CLK_PHA BIT(6) #define MVEBU_SPI_A3700_CLK_POL BIT(7) #define MVEBU_SPI_A3700_FIFO_EN BIT(17) #define MVEBU_SPI_A3700_SPI_EN_0 BIT(16) #define MVEBU_SPI_A3700_CLK_PRESCALE_MASK 0x1f #define MAX_CS_COUNT 4 /* SPI registers */ struct spi_reg { u32 ctrl; /* 0x10600 */ u32 cfg; /* 0x10604 */ u32 dout; /* 0x10608 */ u32 din; /* 0x1060c */ }; struct mvebu_spi_platdata { struct spi_reg *spireg; struct clk clk; struct gpio_desc cs_gpios[MAX_CS_COUNT]; }; static void spi_cs_activate(struct mvebu_spi_platdata *plat, int cs) { if (CONFIG_IS_ENABLED(DM_GPIO) && dm_gpio_is_valid(&plat->cs_gpios[cs])) dm_gpio_set_value(&plat->cs_gpios[cs], 1); else setbits_le32(&plat->spireg->ctrl, MVEBU_SPI_A3700_SPI_EN_0 << cs); } static void spi_cs_deactivate(struct mvebu_spi_platdata *plat, int cs) { if (CONFIG_IS_ENABLED(DM_GPIO) && dm_gpio_is_valid(&plat->cs_gpios[cs])) dm_gpio_set_value(&plat->cs_gpios[cs], 0); else clrbits_le32(&plat->spireg->ctrl, MVEBU_SPI_A3700_SPI_EN_0 << cs); } /** * spi_legacy_shift_byte() - triggers the real SPI transfer * @bytelen: Indicate how many bytes to transfer. * @dout: Buffer address of what to send. * @din: Buffer address of where to receive. * * This function triggers the real SPI transfer in legacy mode. It * will shift out char buffer from @dout, and shift in char buffer to * @din, if necessary. * * This function assumes that only one byte is shifted at one time. * However, it is not its responisbility to set the transfer type to * one-byte. Also, it does not guarantee that it will work if transfer * type becomes two-byte. See spi_set_legacy() for details. * * In legacy mode, simply write to the SPI_DOUT register will trigger * the transfer. * * If @dout == NULL, which means no actual data needs to be sent out, * then the function will shift out 0x00 in order to shift in data. * The XFER_RDY flag is checked every time before accessing SPI_DOUT * and SPI_DIN register. * * The number of transfers to be triggerred is decided by @bytelen. * * Return: 0 - cool * -ETIMEDOUT - XFER_RDY flag timeout */ static int spi_legacy_shift_byte(struct spi_reg *reg, unsigned int bytelen, const void *dout, void *din) { const u8 *dout_8; u8 *din_8; int ret; /* Use 0x00 as dummy dout */ const u8 dummy_dout = 0x0; u32 pending_dout = 0x0; /* dout_8: pointer of current dout */ dout_8 = dout; /* din_8: pointer of current din */ din_8 = din; while (bytelen) { ret = wait_for_bit_le32(®->ctrl, MVEBU_SPI_A3700_XFER_RDY, true,100, false); if (ret) return ret; if (dout) pending_dout = (u32)*dout_8; else pending_dout = (u32)dummy_dout; /* Trigger the xfer */ writel(pending_dout, ®->dout); if (din) { ret = wait_for_bit_le32(®->ctrl, MVEBU_SPI_A3700_XFER_RDY, true, 100, false); if (ret) return ret; /* Read what is transferred in */ *din_8 = (u8)readl(®->din); } /* Don't increment the current pointer if NULL */ if (dout) dout_8++; if (din) din_8++; bytelen--; } return 0; } static int mvebu_spi_xfer(struct udevice *dev, unsigned int bitlen, const void *dout, void *din, unsigned long flags) { struct udevice *bus = dev->parent; struct mvebu_spi_platdata *plat = dev_get_platdata(bus); struct spi_reg *reg = plat->spireg; unsigned int bytelen; int ret; bytelen = bitlen / 8; if (dout && din) debug("This is a duplex transfer.\n"); /* Activate CS */ if (flags & SPI_XFER_BEGIN) { debug("SPI: activate cs.\n"); spi_cs_activate(plat, spi_chip_select(dev)); } /* Send and/or receive */ if (dout || din) { ret = spi_legacy_shift_byte(reg, bytelen, dout, din); if (ret) return ret; } /* Deactivate CS */ if (flags & SPI_XFER_END) { ret = wait_for_bit_le32(®->ctrl, MVEBU_SPI_A3700_XFER_RDY, true, 100, false); if (ret) return ret; debug("SPI: deactivate cs.\n"); spi_cs_deactivate(plat, spi_chip_select(dev)); } return 0; } static int mvebu_spi_set_speed(struct udevice *bus, uint hz) { struct mvebu_spi_platdata *plat = dev_get_platdata(bus); struct spi_reg *reg = plat->spireg; u32 data, prescale; data = readl(®->cfg); prescale = DIV_ROUND_UP(clk_get_rate(&plat->clk), hz); if (prescale > 0xf) prescale = 0x10 + (prescale + 1) / 2; prescale = min(prescale, 0x1fu); data &= ~MVEBU_SPI_A3700_CLK_PRESCALE_MASK; data |= prescale & MVEBU_SPI_A3700_CLK_PRESCALE_MASK; writel(data, ®->cfg); return 0; } static int mvebu_spi_set_mode(struct udevice *bus, uint mode) { struct mvebu_spi_platdata *plat = dev_get_platdata(bus); struct spi_reg *reg = plat->spireg; /* * Set SPI polarity * 0: Serial interface clock is low when inactive * 1: Serial interface clock is high when inactive */ if (mode & SPI_CPOL) setbits_le32(®->cfg, MVEBU_SPI_A3700_CLK_POL); else clrbits_le32(®->cfg, MVEBU_SPI_A3700_CLK_POL); if (mode & SPI_CPHA) setbits_le32(®->cfg, MVEBU_SPI_A3700_CLK_PHA); else clrbits_le32(®->cfg, MVEBU_SPI_A3700_CLK_PHA); return 0; } static int mvebu_spi_probe(struct udevice *bus) { struct mvebu_spi_platdata *plat = dev_get_platdata(bus); struct spi_reg *reg = plat->spireg; u32 data; int ret; /* * Settings SPI controller to be working in legacy mode, which * means use only DO pin (I/O 1) for Data Out, and DI pin (I/O 0) * for Data In. */ /* Flush read/write FIFO */ data = readl(®->cfg); writel(data | MVEBU_SPI_A3700_FIFO_FLUSH, ®->cfg); ret = wait_for_bit_le32(®->cfg, MVEBU_SPI_A3700_FIFO_FLUSH, false, 1000, false); if (ret) return ret; /* Disable FIFO mode */ data &= ~MVEBU_SPI_A3700_FIFO_EN; /* Always shift 1 byte at a time */ data &= ~MVEBU_SPI_A3700_BYTE_LEN; writel(data, ®->cfg); /* Set up CS GPIOs in device tree, if any */ if (CONFIG_IS_ENABLED(DM_GPIO) && gpio_get_list_count(bus, "cs-gpios") > 0) { int i; for (i = 0; i < ARRAY_SIZE(plat->cs_gpios); i++) { ret = gpio_request_by_name(bus, "cs-gpios", i, &plat->cs_gpios[i], 0); if (ret < 0 || !dm_gpio_is_valid(&plat->cs_gpios[i])) { /* Use the native CS function for this line */ continue; } ret = dm_gpio_set_dir_flags(&plat->cs_gpios[i], GPIOD_IS_OUT | GPIOD_ACTIVE_LOW); if (ret) { dev_err(bus, "Setting cs %d error\n", i); return ret; } } } return 0; } static int mvebu_spi_ofdata_to_platdata(struct udevice *bus) { struct mvebu_spi_platdata *plat = dev_get_platdata(bus); int ret; plat->spireg = dev_read_addr_ptr(bus); ret = clk_get_by_index(bus, 0, &plat->clk); if (ret) { dev_err(bus, "cannot get clock\n"); return ret; } return 0; } static int mvebu_spi_remove(struct udevice *bus) { struct mvebu_spi_platdata *plat = dev_get_platdata(bus); clk_free(&plat->clk); return 0; } static const struct dm_spi_ops mvebu_spi_ops = { .xfer = mvebu_spi_xfer, .set_speed = mvebu_spi_set_speed, .set_mode = mvebu_spi_set_mode, /* * cs_info is not needed, since we require all chip selects to be * in the device tree explicitly */ }; static const struct udevice_id mvebu_spi_ids[] = { { .compatible = "marvell,armada-3700-spi" }, { } }; U_BOOT_DRIVER(mvebu_spi) = { .name = "mvebu_spi", .id = UCLASS_SPI, .of_match = mvebu_spi_ids, .ops = &mvebu_spi_ops, .ofdata_to_platdata = mvebu_spi_ofdata_to_platdata, .platdata_auto = sizeof(struct mvebu_spi_platdata), .probe = mvebu_spi_probe, .remove = mvebu_spi_remove, };