[RFC linux 1/2] spi: aspeed: added generic spi master for Aspeed
Brendan Higgins
brendanhiggins at google.com
Wed Dec 7 10:36:02 AEDT 2016
Added initial generic spi master support to the Aspeed 25XX SoCs.
Signed-off-by: Brendan Higgins <brendanhiggins at google.com>
---
drivers/spi/Kconfig | 12 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-aspeed.c | 582 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 595 insertions(+)
create mode 100644 drivers/spi/spi-aspeed.c
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 4b931ec..c9dcc71 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -59,6 +59,18 @@ config SPI_ALTERA
help
This is the driver for the Altera SPI Controller.
+config SPI_ASPEED
+ tristate "Aspeed AST24XX/AST25XX SPI controller driver"
+ depends on ARCH_ASPEED
+ help
+ This enables support for the SPI controller on the Aspeed AST25XX SoCs
+ as a generic SPI master.
+
+ The Aspeed AST25XX SoCs have three SPI controllers intended as SPI
+ flash controllers; nevertheless, they are flexible enough to support
+ the Linux SPI master framework and are physically capable of
+ supporting some other SPI devices.
+
config SPI_ATH79
tristate "Atheros AR71XX/AR724X/AR913X SPI controller driver"
depends on ATH79 && GPIOLIB
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 3c74d00..6b49aed 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o
# SPI master controller drivers (bus)
obj-$(CONFIG_SPI_ALTERA) += spi-altera.o
+obj-$(CONFIG_SPI_ASPEED) += spi-aspeed.o
obj-$(CONFIG_SPI_ATMEL) += spi-atmel.o
obj-$(CONFIG_SPI_ATH79) += spi-ath79.o
obj-$(CONFIG_SPI_AU1550) += spi-au1550.o
diff --git a/drivers/spi/spi-aspeed.c b/drivers/spi/spi-aspeed.c
new file mode 100644
index 0000000..80c941a
--- /dev/null
+++ b/drivers/spi/spi-aspeed.c
@@ -0,0 +1,582 @@
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/spi/spi.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+
+#define ASPEED_SPI_CONFIG_REG 0x00
+#define ASPEED_SPI_CONFIG_WRITE_CE1_EN BIT(17)
+#define ASPEED_SPI_CONFIG_WRITE_CE0_EN BIT(16)
+#define ASPEED_SPI_CE_CTRL_REG 0x04
+#define ASPEED_SPI_CE_CTRL_CE_SWAP BIT(31)
+#define ASPEED_SPI_CE_CTRL_CE1_HALF_SPEED_EN BIT(9)
+#define ASPEED_SPI_CE_CTRL_CE0_HALF_SPEED_EN BIT(8)
+#define ASPEED_SPI_CE_CTRL_CE1_4_BYTE_ADDR_EN BIT(1)
+#define ASPEED_SPI_CE_CTRL_CE0_4_BYTE_ADDR_EN BIT(0)
+#define ASPEED_SPI_IRQ_REG 0x08
+#define ASPEED_SPI_IRQ_CMD_ABORT_STS BIT(10)
+#define ASPEED_SPI_IRQ_WRITE_PROTECTED_STS BIT(9)
+#define ASPEED_SPI_IRQ_CMD_ABORT_EN BIT(2)
+#define ASPEED_SPI_IRQ_WRITE_PROTECTED_EN BIT(1)
+#define ASPEED_SPI_CMD_CTRL_REG 0x0c
+#define ASPEED_SPI_CMD_CTRL_ADDR_BYTE_LANE_DIS_MASK GENMASK(7, 4)
+#define ASPEED_SPI_CMD_CTRL_DATA_BYTE_LANE_DIS_MASK GENMASK(3, 0)
+#define ASPEED_SPI_CE0_CTRL_REG 0x10
+#define ASPEED_SPI_CE1_CTRL_REG 0x14
+#define ASPEED_SPI_CEX_CTRL_IO_MODE_MASK GENMASK(29, 28)
+#define ASPEED_SPI_CEX_CTRL_INACTIVE_PULSE_WIDTH GENMASK(27, 24)
+#define ASPEED_SPI_CEX_CTRL_CMD_DATA GENMASK(23, 16)
+#define ASPEED_SPI_CEX_CTRL_DUMMY_CYCLE_CMD_OUT_EN BIT(15)
+#define ASPEED_SPI_CEX_CTRL_FAST_READ_DUMMY_CYCLE_EN BIT(14)
+#define ASPEED_SPI_CEX_CTRL_DIV_CLK_BY_4 BIT(13)
+#define ASPEED_SPI_CEX_CTRL_FLASH_RD_WR_MERGE_DIS BIT(12)
+#define ASPEED_SPI_CEX_CTRL_CLK_DIV_SHIFT 8
+#define ASPEED_SPI_CEX_CTRL_CLK_DIV GENMASK(11, 8)
+#define ASPEED_SPI_CEX_CTRL_FAST_READ_DUMMY_CYCLE_NUM GENMASK(7, 6)
+#define ASPEED_SPI_CEX_CTRL_LSB_EN BIT(5)
+#define ASPEED_SPI_CEX_CTRL_CLK_MODE_3_ON_START_EN BIT(4)
+#define ASPEED_SPI_CEX_CTRL_DUAL_DATA_MODE_EN BIT(3)
+#define ASPEED_SPI_CEX_CTRL_STOP_ACTIVE BIT(2)
+#define ASPEED_SPI_CEX_CTRL_USER_CMD_MODE (BIT(0) | BIT(1))
+#define ASPEED_SPI_CE0_ADDR_REG 0x30
+#define ASPEED_SPI_CE1_ADDR_REG 0x34
+#define ASPEED_SPI_CEX_ADDR_END_HIGH_MASK GENMASK(31, 28)
+#define ASPEED_SPI_CEX_ADDR_END_LOW_MASK GENMASK(27, 24)
+#define ASPEED_SPI_CEX_ADDR_START_HIGH_MASK GENMASK(23, 20)
+#define ASPEED_SPI_CEX_ADDR_START_LOW_MASK GENMASK(19, 16)
+#define ASPEED_SPI_CEX_ADDR_MASK GENMASK(30, 23)
+#define ASPEED_SPI_DUMMY_CYCLE_REG 0x54
+#define ASPEED_SPI_DUMMY_CYCLE_DATA GENMASK(7, 0)
+#define ASPEED_SPI_READ_TIME_REG 0x94
+#define ASPEED_SPI_READ_TIME_NO_INPUT_DELAY_CYCLE_MASK GENMASK(19, 0)
+
+#define ASPEED_SPI_ADDRS_TO_CEX_ADDR_REG(start, end) \
+ ((((end) & ASPEED_SPI_CEX_ADDR_MASK) << 1) | \
+ (((start) & ASPEED_SPI_CEX_ADDR_MASK) >> 7))
+
+#define ASPEED_SPI_BUS_NUM 2
+
+/*
+ * TODO: This is taken from drivers/mtd/spi-nor/aspeed-smc.c we should discuss
+ * sharing code between the two implementations. It appears that there is a
+ * spi_device implementation of a spi_nor device, so it probably makes sense to
+ * just move the entire non-dma implementation here.
+ *
+ * In user mode all data bytes read or written to the chip decode address
+ * range are transferred to or from the SPI bus. The range is treated as a
+ * fifo of arbitratry 1, 2, or 4 byte width but each write has to be aligned
+ * to its size. The address within the multiple 8kB range is ignored when
+ * sending bytes to the SPI bus.
+ *
+ * On the arm architecture, as of Linux version 4.3, memcpy_fromio and
+ * memcpy_toio on little endian targets use the optimized memcpy routines
+ * that were designed for well behavied memory storage. These routines
+ * have a stutter if the source and destination are not both word aligned,
+ * once with a duplicate access to the source after aligning to the
+ * destination to a word boundary, and again with a duplicate access to
+ * the source when the final byte count is not word aligned.
+ *
+ * When writing or reading the fifo this stutter discards data or sends
+ * too much data to the fifo and can not be used by this driver.
+ *
+ * While the low level io string routines that implement the insl family do
+ * the desired accesses and memory increments, the cross architecture io
+ * macros make them essentially impossible to use on a memory mapped address
+ * instead of a a token from the call to iomap of an io port.
+ *
+ * These fifo routines use readl and friends to a constant io port and update
+ * the memory buffer pointer and count via explicit code. The final updates
+ * to len are optimistically suppressed.
+ */
+static int aspeed_spi_read_from_ahb(void *buf, const void __iomem *src,
+ size_t len)
+{
+ if ((((unsigned long) src | (unsigned long) buf | len) & 3) == 0) {
+ while (len > 3) {
+ *(u32 *) buf = readl(src);
+ buf += 4;
+ src += 4;
+ len -= 4;
+ }
+ }
+
+ while (len--) {
+ *(u8 *) buf = readb(src);
+ buf += 1;
+ src += 1;
+ }
+ return 0;
+}
+
+static int aspeed_spi_write_to_ahb(void __iomem *dst, const void *buf,
+ size_t len)
+{
+ if ((((unsigned long) dst | (unsigned long) buf | len) & 3) == 0) {
+ while (len > 3) {
+ u32 val = *(u32 *) buf;
+
+ writel(val, dst);
+ buf += 4;
+ dst += 4;
+ len -= 4;
+ }
+ }
+
+ while (len--) {
+ u8 val = *(u8 *) buf;
+
+ writeb(val, dst);
+ buf += 1;
+ dst += 1;
+ }
+ return 0;
+}
+
+struct aspeed_spi_bus {
+ void __iomem *cs_ctrl_reg;
+ void __iomem *cs_addr_reg;
+ void __iomem *io_port;
+ phys_addr_t io_port_pa;
+ size_t port_size;
+};
+
+struct aspeed_spi_master {
+ struct device *dev;
+ void __iomem *base;
+ struct aspeed_spi_bus bus[ASPEED_SPI_BUS_NUM];
+};
+
+static u32 read_reg(struct aspeed_spi_master *master, loff_t offset)
+{
+ return readl(master->base + offset);
+}
+
+static void write_reg(u32 value, struct aspeed_spi_master *master,
+ loff_t offset)
+{
+ writel(value, master->base + offset);
+}
+
+static irqreturn_t aspeed_spi_irq(int irq, void *arg)
+{
+ struct aspeed_spi_master *master = arg;
+ u32 irq_reg;
+
+ dev_err(master->dev, "received error interrupt.\n");
+
+ irq_reg = read_reg(master, ASPEED_SPI_IRQ_REG);
+
+ if (irq_reg & ASPEED_SPI_IRQ_CMD_ABORT_STS)
+ dev_err(master->dev, "command aborted.\n");
+
+ /* This error should never occur as we do not use the addressed r/w
+ * features of the controller in this driver.
+ */
+ if (irq_reg & ASPEED_SPI_IRQ_WRITE_PROTECTED_STS)
+ dev_err(master->dev, "attempted write to protected address.\n");
+
+ /* Clear handled interrupts. */
+ write_reg(irq_reg, master, ASPEED_SPI_IRQ_REG);
+ return IRQ_HANDLED;
+}
+
+static size_t aspeed_spi_max_transfer_size(struct spi_device *device)
+{
+ struct aspeed_spi_master *master =
+ spi_master_get_devdata(device->master);
+
+ if (unlikely(device->chip_select > 1)) {
+ WARN(true, "attempted to set invalid chip select: %d\n",
+ device->chip_select);
+ return 0;
+ }
+
+ return master->bus[device->chip_select].port_size;
+}
+
+static void aspeed_spi_set_cs(struct spi_device *device, bool cs_logic_level)
+{
+ struct aspeed_spi_master *master =
+ spi_master_get_devdata(device->master);
+ struct aspeed_spi_bus *bus;
+ u32 cs_ctrl;
+
+ if (unlikely(device->chip_select > 1)) {
+ WARN(true, "attempted to set invalid chip select: %d\n",
+ device->chip_select);
+ return;
+ }
+ bus = &master->bus[device->chip_select];
+
+ cs_ctrl = readl(bus->cs_ctrl_reg);
+ /* device is requesting CS go low (active), so we need to configure the
+ * bus according to that device's settings. cs_ctrl is a per bus/device
+ * register, so it is safe to update these on chip select.
+ */
+ if (!cs_logic_level) {
+ if ((device->mode & (SPI_CPOL | SPI_CPHA)) == SPI_MODE_3)
+ cs_ctrl |= ASPEED_SPI_CEX_CTRL_CLK_MODE_3_ON_START_EN;
+ else /* SPI_MODE_0 by default */
+ cs_ctrl &= ~ASPEED_SPI_CEX_CTRL_CLK_MODE_3_ON_START_EN;
+
+ if (device->mode & SPI_LSB_FIRST)
+ cs_ctrl |= ASPEED_SPI_CEX_CTRL_LSB_EN;
+ else /* MSB first by default */
+ cs_ctrl &= ~ASPEED_SPI_CEX_CTRL_LSB_EN;
+
+ cs_ctrl &= ~ASPEED_SPI_CEX_CTRL_STOP_ACTIVE;
+ cs_ctrl |= ASPEED_SPI_CEX_CTRL_USER_CMD_MODE;
+
+ writel(cs_ctrl, bus->cs_ctrl_reg);
+ } else
+ writel(cs_ctrl | ASPEED_SPI_CEX_CTRL_STOP_ACTIVE,
+ bus->cs_ctrl_reg);
+}
+
+static u8 aspeed_spi_compute_divider(u32 requested_speed, u32 base_speed)
+{
+ u32 divider;
+
+ if (requested_speed > base_speed)
+ return 0xf; /* no divider */
+
+ divider = base_speed / requested_speed;
+ if (base_speed % requested_speed > 0)
+ divider++;
+
+ if (divider > 16) {
+ WARN_ONCE(true,
+ "device requested clock speed, %u, less than minimum clock speed, %u.\n",
+ requested_speed, base_speed / 16);
+ divider = 16;
+ }
+
+ if (divider % 2 == 0)
+ return (16 - divider) / 2;
+ else
+ return 8 + (15 - divider) / 2;
+}
+
+static void aspeed_spi_update_clk_for_xfer(struct spi_master *spi_master,
+ struct spi_device *device,
+ struct spi_transfer *xfer)
+{
+ struct aspeed_spi_master *master = spi_master_get_devdata(spi_master);
+ struct aspeed_spi_bus *bus = &master->bus[device->chip_select];
+ u32 requested_speed, base_speed = spi_master->max_speed_hz, reg_val;
+ u8 divider;
+
+ if (xfer->speed_hz)
+ requested_speed = xfer->speed_hz;
+ else
+ requested_speed = device->max_speed_hz;
+ divider = aspeed_spi_compute_divider(requested_speed, base_speed);
+
+ reg_val = readl(bus->cs_ctrl_reg);
+ reg_val &= ~ASPEED_SPI_CEX_CTRL_CLK_DIV;
+ reg_val |= divider << ASPEED_SPI_CEX_CTRL_CLK_DIV_SHIFT;
+}
+
+static int aspeed_spi_transfer_one(struct spi_master *spi_master,
+ struct spi_device *device,
+ struct spi_transfer *transfer)
+{
+ struct aspeed_spi_master *master = spi_master_get_devdata(spi_master);
+ struct aspeed_spi_bus *bus;
+ int ret = 0;
+
+ if (device->chip_select > 1) {
+ dev_err(master->dev,
+ "attempted to transfer to device with invalid chip select: %d\n",
+ device->chip_select);
+ return -EINVAL;
+ }
+ bus = &master->bus[device->chip_select];
+
+ if (transfer->len > bus->port_size) {
+ dev_err(master->dev,
+ "attempted transfer with larger than supported size: %u\n",
+ transfer->len);
+ return -EINVAL;
+ }
+
+ aspeed_spi_update_clk_for_xfer(spi_master, device, transfer);
+
+ if (transfer->tx_buf)
+ aspeed_spi_write_to_ahb(bus->io_port,
+ transfer->tx_buf, transfer->len);
+
+ if (transfer->rx_buf)
+ aspeed_spi_read_from_ahb(transfer->rx_buf,
+ bus->io_port, transfer->len);
+
+ return ret;
+}
+
+static int aspeed_spi_init(struct aspeed_spi_master *master)
+{
+ u32 start_addr, end_addr, reg_val, partial_reg_val;
+ struct aspeed_spi_bus *bus;
+ int i;
+
+ reg_val = read_reg(master, ASPEED_SPI_CONFIG_REG);
+ reg_val |= ASPEED_SPI_CONFIG_WRITE_CE0_EN |
+ ASPEED_SPI_CONFIG_WRITE_CE1_EN;
+ write_reg(reg_val, master, ASPEED_SPI_CONFIG_REG);
+
+ reg_val = read_reg(master, ASPEED_SPI_CE_CTRL_REG);
+ reg_val &= ~(ASPEED_SPI_CE_CTRL_CE_SWAP |
+ ASPEED_SPI_CE_CTRL_CE1_4_BYTE_ADDR_EN |
+ ASPEED_SPI_CE_CTRL_CE1_4_BYTE_ADDR_EN);
+ reg_val |= ASPEED_SPI_CE_CTRL_CE1_HALF_SPEED_EN |
+ ASPEED_SPI_CE_CTRL_CE0_HALF_SPEED_EN;
+ write_reg(reg_val, master, ASPEED_SPI_CE_CTRL_REG);
+
+ reg_val = read_reg(master, ASPEED_SPI_IRQ_REG);
+ reg_val |= ASPEED_SPI_IRQ_CMD_ABORT_EN |
+ ASPEED_SPI_IRQ_WRITE_PROTECTED_EN;
+ write_reg(reg_val, master, ASPEED_SPI_IRQ_REG);
+ /* Clear any interrupt status bits from previous operation. */
+ reg_val |= ASPEED_SPI_IRQ_CMD_ABORT_STS |
+ ASPEED_SPI_IRQ_WRITE_PROTECTED_STS;
+ write_reg(reg_val, master, ASPEED_SPI_IRQ_REG);
+
+ reg_val = read_reg(master, ASPEED_SPI_CMD_CTRL_REG);
+ reg_val &= ~(ASPEED_SPI_CMD_CTRL_ADDR_BYTE_LANE_DIS_MASK |
+ ASPEED_SPI_CMD_CTRL_DATA_BYTE_LANE_DIS_MASK);
+ write_reg(reg_val, master, ASPEED_SPI_CMD_CTRL_REG);
+
+ for (i = 0; i < ASPEED_SPI_BUS_NUM; i++) {
+ bus = &master->bus[i];
+
+ reg_val = readl(bus->cs_ctrl_reg);
+ reg_val &= ~(ASPEED_SPI_CEX_CTRL_IO_MODE_MASK |
+ ASPEED_SPI_CEX_CTRL_INACTIVE_PULSE_WIDTH |
+ ASPEED_SPI_CEX_CTRL_CMD_DATA |
+ ASPEED_SPI_CEX_CTRL_DUMMY_CYCLE_CMD_OUT_EN |
+ ASPEED_SPI_CEX_CTRL_FAST_READ_DUMMY_CYCLE_EN |
+ ASPEED_SPI_CEX_CTRL_DIV_CLK_BY_4 |
+ ASPEED_SPI_CEX_CTRL_CLK_DIV |
+ ASPEED_SPI_CEX_CTRL_FAST_READ_DUMMY_CYCLE_NUM |
+ ASPEED_SPI_CEX_CTRL_LSB_EN |
+ ASPEED_SPI_CEX_CTRL_CLK_MODE_3_ON_START_EN |
+ ASPEED_SPI_CEX_CTRL_DUAL_DATA_MODE_EN);
+ reg_val |= ASPEED_SPI_CEX_CTRL_FLASH_RD_WR_MERGE_DIS |
+ ASPEED_SPI_CEX_CTRL_STOP_ACTIVE |
+ ASPEED_SPI_CEX_CTRL_USER_CMD_MODE;
+
+ /* The SPI controller needs to know about where the ports used
+ * to access it are as it is actually designed to be a SPI flash
+ * controller. It provides a range that the ports can live in
+ * and then allows a sub-range within that range to be defined
+ * as the actual ports.
+ *
+ * So we
+ * 1) find the address range we were given for the IO ports
+ * 2) convert the address range to the same format as the
+ * register
+ * 3) compare the portions which cannot be modified to verify
+ * the range is valid
+ * 4) write back the portions that can be modified
+ */
+ reg_val = readl(bus->cs_addr_reg);
+ start_addr = (u32) bus->io_port_pa;
+ end_addr = start_addr + bus->port_size;
+ partial_reg_val = ASPEED_SPI_ADDRS_TO_CEX_ADDR_REG(start_addr,
+ end_addr);
+ if ((partial_reg_val & (ASPEED_SPI_CEX_ADDR_END_HIGH_MASK |
+ ASPEED_SPI_CEX_ADDR_START_HIGH_MASK)) !=
+ (reg_val & (ASPEED_SPI_CEX_ADDR_END_HIGH_MASK |
+ ASPEED_SPI_CEX_ADDR_START_HIGH_MASK))) {
+ dev_err(master->dev,
+ "invalid spi port address range requested for CE%d: 0x%06x - 0x%06x\n",
+ i, start_addr, end_addr);
+ return -EINVAL;
+ }
+ reg_val &= ~(ASPEED_SPI_CEX_ADDR_END_LOW_MASK |
+ ASPEED_SPI_CEX_ADDR_START_LOW_MASK);
+ reg_val |= partial_reg_val &
+ (ASPEED_SPI_CEX_ADDR_END_LOW_MASK |
+ ASPEED_SPI_CEX_ADDR_START_LOW_MASK);
+ writel(reg_val, bus->cs_addr_reg);
+ }
+
+ reg_val = read_reg(master, ASPEED_SPI_DUMMY_CYCLE_REG);
+ reg_val &= ~ASPEED_SPI_DUMMY_CYCLE_DATA;
+ write_reg(reg_val, master, ASPEED_SPI_DUMMY_CYCLE_REG);
+
+ reg_val = read_reg(master, ASPEED_SPI_READ_TIME_REG);
+ reg_val &= ~ASPEED_SPI_READ_TIME_NO_INPUT_DELAY_CYCLE_MASK;
+ write_reg(reg_val, master, ASPEED_SPI_READ_TIME_REG);
+
+ return 0;
+}
+
+static int aspeed_spi_probe(struct platform_device *pdev)
+{
+ struct aspeed_spi_master *aspeed_spi_master;
+ struct spi_master *spi_master;
+ struct aspeed_spi_bus *bus;
+ struct resource *res;
+ int irq, ret = 0;
+
+ spi_master = spi_alloc_master(&pdev->dev,
+ sizeof(struct aspeed_spi_master));
+ if (!spi_master)
+ return -ENOMEM;
+ aspeed_spi_master = spi_master_get_devdata(spi_master);
+ platform_set_drvdata(pdev, spi_master);
+
+ aspeed_spi_master->dev = &pdev->dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "could not get register memory.\n");
+ ret = -ENOMEM;
+ goto err_out;
+ }
+ aspeed_spi_master->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(aspeed_spi_master->base)) {
+ dev_err(&pdev->dev, "could not ioremap register memory.\n");
+ ret = PTR_ERR(aspeed_spi_master->base);
+ goto err_out;
+ }
+
+ bus = &aspeed_spi_master->bus[0];
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res) {
+ dev_err(&pdev->dev, "could not get port memory.\n");
+ ret = -ENOMEM;
+ goto err_out;
+ }
+ bus->cs_ctrl_reg = aspeed_spi_master->base + ASPEED_SPI_CE0_CTRL_REG;
+ bus->cs_addr_reg = aspeed_spi_master->base + ASPEED_SPI_CE0_ADDR_REG;
+ bus->io_port = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(bus->io_port)) {
+ dev_err(&pdev->dev, "could not ioremap port.\n");
+ ret = PTR_ERR(bus->io_port);
+ goto err_out;
+ }
+ bus->io_port_pa = res->start;
+ bus->port_size = resource_size(res);
+
+ /* TODO: Maybe we should not require a separate port window to be
+ * specified as a user might not want to use all chip selects. In any
+ * case, we should support the third chipselect for the fmc.
+ */
+ bus = &aspeed_spi_master->bus[1];
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (!res) {
+ dev_err(&pdev->dev, "could not get port memory.\n");
+ ret = -ENOMEM;
+ goto err_out;
+ }
+ bus->cs_ctrl_reg = aspeed_spi_master->base + ASPEED_SPI_CE1_CTRL_REG;
+ bus->cs_addr_reg = aspeed_spi_master->base + ASPEED_SPI_CE1_ADDR_REG;
+ bus->io_port = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(bus->io_port)) {
+ dev_err(&pdev->dev, "could not ioremap port.\n");
+ ret = PTR_ERR(bus->io_port);
+ goto err_out;
+ }
+ bus->io_port_pa = res->start;
+ bus->port_size = resource_size(res);
+
+ /* This should happen before initializing the IRQ handler as this could
+ * clear left over IRQs from previous operation.
+ */
+ ret = aspeed_spi_init(aspeed_spi_master);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not initialize controller.\n");
+ goto err_out;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "could not get irq.\n");
+ ret = irq;
+ goto err_out;
+ }
+ ret = devm_request_irq(&pdev->dev, irq, aspeed_spi_irq, IRQF_SHARED,
+ dev_name(&pdev->dev), aspeed_spi_master);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not request irq.\n");
+ goto err_out;
+ }
+
+ spi_master->num_chipselect = ASPEED_SPI_BUS_NUM;
+ /* TODO: Should also be able to support 2 bit transfers. */
+ spi_master->mode_bits = SPI_MODE_0 | SPI_MODE_3 | SPI_LSB_FIRST;
+ spi_master->bits_per_word_mask = SPI_BPW_MASK(8);
+ /* TODO: This is just the clock speed of the AHB; should we make a fixed
+ * frequency clock for the AHB and use that here?
+ */
+ spi_master->max_speed_hz = 100000000;
+ spi_master->min_speed_hz = spi_master->max_speed_hz / 16;
+ spi_master->flags = SPI_MASTER_HALF_DUPLEX;
+
+ spi_master->max_transfer_size = aspeed_spi_max_transfer_size;
+ spi_master->set_cs = aspeed_spi_set_cs;
+ spi_master->transfer_one = aspeed_spi_transfer_one;
+ spi_master->dev.of_node = pdev->dev.of_node;
+
+ platform_set_drvdata(pdev, spi_master);
+ ret = devm_spi_register_master(&pdev->dev, spi_master);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register spi master.\n");
+ goto err_out;
+ }
+ dev_info(&pdev->dev, "registered spi master.\n");
+ return 0;
+
+err_out:
+ spi_master_put(spi_master);
+ return ret;
+}
+
+static int aspeed_spi_remove(struct platform_device *pdev)
+{
+ struct spi_master *spi_master = platform_get_drvdata(pdev);
+ struct aspeed_spi_master *master = spi_master_get_devdata(spi_master);
+ struct aspeed_spi_bus *bus;
+ u32 reg_val;
+ int i;
+
+ reg_val = read_reg(master, ASPEED_SPI_IRQ_REG);
+ reg_val &= ~(ASPEED_SPI_IRQ_CMD_ABORT_EN |
+ ASPEED_SPI_IRQ_WRITE_PROTECTED_EN);
+ write_reg(reg_val, master, ASPEED_SPI_IRQ_REG);
+
+ for (i = 0; i < ASPEED_SPI_BUS_NUM; i++) {
+ bus = &master->bus[i];
+ reg_val = readl(bus->cs_ctrl_reg);
+ writel(reg_val | ASPEED_SPI_CEX_CTRL_STOP_ACTIVE,
+ bus->cs_ctrl_reg);
+ }
+
+ spi_master_put(spi_master);
+
+ return 0;
+}
+
+static const struct of_device_id aspeed_spi_ids[] = {
+ { .compatible = "aspeed,ast2500-spi" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, aspeed_spi_ids);
+
+static struct platform_driver aspeed_spi_driver = {
+ .driver = {
+ .name = "aspeed-spi",
+ .of_match_table = aspeed_spi_ids,
+ },
+ .probe = aspeed_spi_probe,
+ .remove = aspeed_spi_remove,
+};
+module_platform_driver(aspeed_spi_driver);
+
+MODULE_DESCRIPTION("Aspeed 25XX generic half-duplex SPI driver");
+MODULE_AUTHOR("Brendan Higgins <brendanhiggins at google.com>");
+MODULE_LICENSE("GPL");
--
2.8.0.rc3.226.g39d4020
More information about the openbmc
mailing list