diff --git a/Documentation/devicetree/bindings/spi/spi-meson.txt b/Documentation/devicetree/bindings/spi/spi-meson.txt index dc6d0313324a..825c39cae74a 100644 --- a/Documentation/devicetree/bindings/spi/spi-meson.txt +++ b/Documentation/devicetree/bindings/spi/spi-meson.txt @@ -20,3 +20,34 @@ Required properties: #address-cells = <1>; #size-cells = <0>; }; + +* SPICC (SPI Communication Controller) + +The Meson SPICC is generic SPI controller for general purpose Full-Duplex +communications with dedicated 16 words RX/TX PIO FIFOs. + +Required properties: + - compatible: should be "amlogic,meson-gx-spicc" on Amlogic GX SoCs. + - reg: physical base address and length of the controller registers + - interrupts: The interrupt specifier + - clock-names: Must contain "core" + - clocks: phandle of the input clock for the baud rate generator + - #address-cells: should be 1 + - #size-cells: should be 0 + +Optional properties: + - resets: phandle of the internal reset line + +See ../spi/spi-bus.txt for more details on SPI bus master and slave devices +required and optional properties. + +Example : + spi@c1108d80 { + compatible = "amlogic,meson-gx-spicc"; + reg = <0xc1108d80 0x80>; + interrupts = ; + clock-names = "core"; + clocks = <&clk81>; + #address-cells = <1>; + #size-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/spi/spi-mt65xx.txt b/Documentation/devicetree/bindings/spi/spi-mt65xx.txt index e43f4cf4cf35..e0318cf92d73 100644 --- a/Documentation/devicetree/bindings/spi/spi-mt65xx.txt +++ b/Documentation/devicetree/bindings/spi/spi-mt65xx.txt @@ -3,7 +3,9 @@ Binding for MTK SPI controller Required properties: - compatible: should be one of the following. - mediatek,mt2701-spi: for mt2701 platforms + - mediatek,mt2712-spi: for mt2712 platforms - mediatek,mt6589-spi: for mt6589 platforms + - mediatek,mt7622-spi: for mt7622 platforms - mediatek,mt8135-spi: for mt8135 platforms - mediatek,mt8173-spi: for mt8173 platforms diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 82cd818aa062..fd1b4fdb72a4 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -393,6 +393,13 @@ config SPI_FSL_ESPI From MPC8536, 85xx platform uses the controller, and all P10xx, P20xx, P30xx,P40xx, P50xx uses this controller. +config SPI_MESON_SPICC + tristate "Amlogic Meson SPICC controller" + depends on ARCH_MESON || COMPILE_TEST + help + This enables master mode support for the SPICC (SPI communication + controller) available in Amlogic Meson SoCs. + config SPI_MESON_SPIFC tristate "Amlogic Meson SPIFC controller" depends on ARCH_MESON || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 1d7923e8c63b..31dccfbb335e 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -53,6 +53,7 @@ obj-$(CONFIG_SPI_LANTIQ_SSC) += spi-lantiq-ssc.o obj-$(CONFIG_SPI_JCORE) += spi-jcore.o obj-$(CONFIG_SPI_LM70_LLP) += spi-lm70llp.o obj-$(CONFIG_SPI_LP8841_RTC) += spi-lp8841-rtc.o +obj-$(CONFIG_SPI_MESON_SPICC) += spi-meson-spicc.o obj-$(CONFIG_SPI_MESON_SPIFC) += spi-meson-spifc.o obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o diff --git a/drivers/spi/spi-loopback-test.c b/drivers/spi/spi-loopback-test.c index f4875f177df0..3459965004f8 100644 --- a/drivers/spi/spi-loopback-test.c +++ b/drivers/spi/spi-loopback-test.c @@ -894,7 +894,7 @@ int spi_test_execute_msg(struct spi_device *spi, struct spi_test *test, test->elapsed_time = ktime_to_ns(ktime_sub(ktime_get(), start)); if (ret == -ETIMEDOUT) { dev_info(&spi->dev, - "spi-message timed out - reruning...\n"); + "spi-message timed out - rerunning...\n"); /* rerun after a few explicit schedules */ for (i = 0; i < 16; i++) schedule(); @@ -1021,10 +1021,9 @@ int spi_test_run_tests(struct spi_device *spi, rx = vmalloc(SPI_TEST_MAX_SIZE_PLUS); else rx = kzalloc(SPI_TEST_MAX_SIZE_PLUS, GFP_KERNEL); - if (!rx) { - ret = -ENOMEM; - goto out; - } + if (!rx) + return -ENOMEM; + if (use_vmalloc) tx = vmalloc(SPI_TEST_MAX_SIZE_PLUS); @@ -1032,7 +1031,7 @@ int spi_test_run_tests(struct spi_device *spi, tx = kzalloc(SPI_TEST_MAX_SIZE_PLUS, GFP_KERNEL); if (!tx) { ret = -ENOMEM; - goto out; + goto err_tx; } /* now run the individual tests in the table */ @@ -1057,8 +1056,9 @@ int spi_test_run_tests(struct spi_device *spi, } out: - kvfree(rx); kvfree(tx); +err_tx: + kvfree(rx); return ret; } EXPORT_SYMBOL_GPL(spi_test_run_tests); diff --git a/drivers/spi/spi-meson-spicc.c b/drivers/spi/spi-meson-spicc.c new file mode 100644 index 000000000000..7f8429635502 --- /dev/null +++ b/drivers/spi/spi-meson-spicc.c @@ -0,0 +1,619 @@ +/* + * Driver for Amlogic Meson SPI communication controller (SPICC) + * + * Copyright (C) BayLibre, SAS + * Author: Neil Armstrong + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The Meson SPICC controller could support DMA based transfers, but is not + * implemented by the vendor code, and while having the registers documentation + * it has never worked on the GXL Hardware. + * The PIO mode is the only mode implemented, and due to badly designed HW : + * - all transfers are cutted in 16 words burst because the FIFO hangs on + * TX underflow, and there is no TX "Half-Empty" interrupt, so we go by + * FIFO max size chunk only + * - CS management is dumb, and goes UP between every burst, so is really a + * "Data Valid" signal than a Chip Select, GPIO link should be used instead + * to have a CS go down over the full transfer + */ + +#define SPICC_MAX_FREQ 30000000 +#define SPICC_MAX_BURST 128 + +/* Register Map */ +#define SPICC_RXDATA 0x00 + +#define SPICC_TXDATA 0x04 + +#define SPICC_CONREG 0x08 +#define SPICC_ENABLE BIT(0) +#define SPICC_MODE_MASTER BIT(1) +#define SPICC_XCH BIT(2) +#define SPICC_SMC BIT(3) +#define SPICC_POL BIT(4) +#define SPICC_PHA BIT(5) +#define SPICC_SSCTL BIT(6) +#define SPICC_SSPOL BIT(7) +#define SPICC_DRCTL_MASK GENMASK(9, 8) +#define SPICC_DRCTL_IGNORE 0 +#define SPICC_DRCTL_FALLING 1 +#define SPICC_DRCTL_LOWLEVEL 2 +#define SPICC_CS_MASK GENMASK(13, 12) +#define SPICC_DATARATE_MASK GENMASK(18, 16) +#define SPICC_DATARATE_DIV4 0 +#define SPICC_DATARATE_DIV8 1 +#define SPICC_DATARATE_DIV16 2 +#define SPICC_DATARATE_DIV32 3 +#define SPICC_BITLENGTH_MASK GENMASK(24, 19) +#define SPICC_BURSTLENGTH_MASK GENMASK(31, 25) + +#define SPICC_INTREG 0x0c +#define SPICC_TE_EN BIT(0) /* TX FIFO Empty Interrupt */ +#define SPICC_TH_EN BIT(1) /* TX FIFO Half-Full Interrupt */ +#define SPICC_TF_EN BIT(2) /* TX FIFO Full Interrupt */ +#define SPICC_RR_EN BIT(3) /* RX FIFO Ready Interrupt */ +#define SPICC_RH_EN BIT(4) /* RX FIFO Half-Full Interrupt */ +#define SPICC_RF_EN BIT(5) /* RX FIFO Full Interrupt */ +#define SPICC_RO_EN BIT(6) /* RX FIFO Overflow Interrupt */ +#define SPICC_TC_EN BIT(7) /* Transfert Complete Interrupt */ + +#define SPICC_DMAREG 0x10 +#define SPICC_DMA_ENABLE BIT(0) +#define SPICC_TXFIFO_THRESHOLD_MASK GENMASK(5, 1) +#define SPICC_RXFIFO_THRESHOLD_MASK GENMASK(10, 6) +#define SPICC_READ_BURST_MASK GENMASK(14, 11) +#define SPICC_WRITE_BURST_MASK GENMASK(18, 15) +#define SPICC_DMA_URGENT BIT(19) +#define SPICC_DMA_THREADID_MASK GENMASK(25, 20) +#define SPICC_DMA_BURSTNUM_MASK GENMASK(31, 26) + +#define SPICC_STATREG 0x14 +#define SPICC_TE BIT(0) /* TX FIFO Empty Interrupt */ +#define SPICC_TH BIT(1) /* TX FIFO Half-Full Interrupt */ +#define SPICC_TF BIT(2) /* TX FIFO Full Interrupt */ +#define SPICC_RR BIT(3) /* RX FIFO Ready Interrupt */ +#define SPICC_RH BIT(4) /* RX FIFO Half-Full Interrupt */ +#define SPICC_RF BIT(5) /* RX FIFO Full Interrupt */ +#define SPICC_RO BIT(6) /* RX FIFO Overflow Interrupt */ +#define SPICC_TC BIT(7) /* Transfert Complete Interrupt */ + +#define SPICC_PERIODREG 0x18 +#define SPICC_PERIOD GENMASK(14, 0) /* Wait cycles */ + +#define SPICC_TESTREG 0x1c +#define SPICC_TXCNT_MASK GENMASK(4, 0) /* TX FIFO Counter */ +#define SPICC_RXCNT_MASK GENMASK(9, 5) /* RX FIFO Counter */ +#define SPICC_SMSTATUS_MASK GENMASK(12, 10) /* State Machine Status */ +#define SPICC_LBC_RO BIT(13) /* Loop Back Control Read-Only */ +#define SPICC_LBC_W1 BIT(14) /* Loop Back Control Write-Only */ +#define SPICC_SWAP_RO BIT(14) /* RX FIFO Data Swap Read-Only */ +#define SPICC_SWAP_W1 BIT(15) /* RX FIFO Data Swap Write-Only */ +#define SPICC_DLYCTL_RO_MASK GENMASK(20, 15) /* Delay Control Read-Only */ +#define SPICC_DLYCTL_W1_MASK GENMASK(21, 16) /* Delay Control Write-Only */ +#define SPICC_FIFORST_RO_MASK GENMASK(22, 21) /* FIFO Softreset Read-Only */ +#define SPICC_FIFORST_W1_MASK GENMASK(23, 22) /* FIFO Softreset Write-Only */ + +#define SPICC_DRADDR 0x20 /* Read Address of DMA */ + +#define SPICC_DWADDR 0x24 /* Write Address of DMA */ + +#define writel_bits_relaxed(mask, val, addr) \ + writel_relaxed((readl_relaxed(addr) & ~(mask)) | (val), addr) + +#define SPICC_BURST_MAX 16 +#define SPICC_FIFO_HALF 10 + +struct meson_spicc_device { + struct spi_master *master; + struct platform_device *pdev; + void __iomem *base; + struct clk *core; + struct spi_message *message; + struct spi_transfer *xfer; + u8 *tx_buf; + u8 *rx_buf; + unsigned int bytes_per_word; + unsigned long tx_remain; + unsigned long txb_remain; + unsigned long rx_remain; + unsigned long rxb_remain; + unsigned long xfer_remain; + bool is_burst_end; + bool is_last_burst; +}; + +static inline bool meson_spicc_txfull(struct meson_spicc_device *spicc) +{ + return !!FIELD_GET(SPICC_TF, + readl_relaxed(spicc->base + SPICC_STATREG)); +} + +static inline bool meson_spicc_rxready(struct meson_spicc_device *spicc) +{ + return FIELD_GET(SPICC_RH | SPICC_RR | SPICC_RF_EN, + readl_relaxed(spicc->base + SPICC_STATREG)); +} + +static inline u32 meson_spicc_pull_data(struct meson_spicc_device *spicc) +{ + unsigned int bytes = spicc->bytes_per_word; + unsigned int byte_shift = 0; + u32 data = 0; + u8 byte; + + while (bytes--) { + byte = *spicc->tx_buf++; + data |= (byte & 0xff) << byte_shift; + byte_shift += 8; + } + + spicc->tx_remain--; + return data; +} + +static inline void meson_spicc_push_data(struct meson_spicc_device *spicc, + u32 data) +{ + unsigned int bytes = spicc->bytes_per_word; + unsigned int byte_shift = 0; + u8 byte; + + while (bytes--) { + byte = (data >> byte_shift) & 0xff; + *spicc->rx_buf++ = byte; + byte_shift += 8; + } + + spicc->rx_remain--; +} + +static inline void meson_spicc_rx(struct meson_spicc_device *spicc) +{ + /* Empty RX FIFO */ + while (spicc->rx_remain && + meson_spicc_rxready(spicc)) + meson_spicc_push_data(spicc, + readl_relaxed(spicc->base + SPICC_RXDATA)); +} + +static inline void meson_spicc_tx(struct meson_spicc_device *spicc) +{ + /* Fill Up TX FIFO */ + while (spicc->tx_remain && + !meson_spicc_txfull(spicc)) + writel_relaxed(meson_spicc_pull_data(spicc), + spicc->base + SPICC_TXDATA); +} + +static inline u32 meson_spicc_setup_rx_irq(struct meson_spicc_device *spicc, + u32 irq_ctrl) +{ + if (spicc->rx_remain > SPICC_FIFO_HALF) + irq_ctrl |= SPICC_RH_EN; + else + irq_ctrl |= SPICC_RR_EN; + + return irq_ctrl; +} + +static inline void meson_spicc_setup_burst(struct meson_spicc_device *spicc, + unsigned int burst_len) +{ + /* Setup Xfer variables */ + spicc->tx_remain = burst_len; + spicc->rx_remain = burst_len; + spicc->xfer_remain -= burst_len * spicc->bytes_per_word; + spicc->is_burst_end = false; + if (burst_len < SPICC_BURST_MAX || !spicc->xfer_remain) + spicc->is_last_burst = true; + else + spicc->is_last_burst = false; + + /* Setup burst length */ + writel_bits_relaxed(SPICC_BURSTLENGTH_MASK, + FIELD_PREP(SPICC_BURSTLENGTH_MASK, + burst_len), + spicc->base + SPICC_CONREG); + + /* Fill TX FIFO */ + meson_spicc_tx(spicc); +} + +static irqreturn_t meson_spicc_irq(int irq, void *data) +{ + struct meson_spicc_device *spicc = (void *) data; + u32 ctrl = readl_relaxed(spicc->base + SPICC_INTREG); + u32 stat = readl_relaxed(spicc->base + SPICC_STATREG) & ctrl; + + ctrl &= ~(SPICC_RH_EN | SPICC_RR_EN); + + /* Empty RX FIFO */ + meson_spicc_rx(spicc); + + /* Enable TC interrupt since we transferred everything */ + if (!spicc->tx_remain && !spicc->rx_remain) { + spicc->is_burst_end = true; + + /* Enable TC interrupt */ + ctrl |= SPICC_TC_EN; + + /* Reload IRQ status */ + stat = readl_relaxed(spicc->base + SPICC_STATREG) & ctrl; + } + + /* Check transfer complete */ + if ((stat & SPICC_TC) && spicc->is_burst_end) { + unsigned int burst_len; + + /* Clear TC bit */ + writel_relaxed(SPICC_TC, spicc->base + SPICC_STATREG); + + /* Disable TC interrupt */ + ctrl &= ~SPICC_TC_EN; + + if (spicc->is_last_burst) { + /* Disable all IRQs */ + writel(0, spicc->base + SPICC_INTREG); + + spi_finalize_current_transfer(spicc->master); + + return IRQ_HANDLED; + } + + burst_len = min_t(unsigned int, + spicc->xfer_remain / spicc->bytes_per_word, + SPICC_BURST_MAX); + + /* Setup burst */ + meson_spicc_setup_burst(spicc, burst_len); + + /* Restart burst */ + writel_bits_relaxed(SPICC_XCH, SPICC_XCH, + spicc->base + SPICC_CONREG); + } + + /* Setup RX interrupt trigger */ + ctrl = meson_spicc_setup_rx_irq(spicc, ctrl); + + /* Reconfigure interrupts */ + writel(ctrl, spicc->base + SPICC_INTREG); + + return IRQ_HANDLED; +} + +static u32 meson_spicc_setup_speed(struct meson_spicc_device *spicc, u32 conf, + u32 speed) +{ + unsigned long parent, value; + unsigned int i, div; + + parent = clk_get_rate(spicc->core); + + /* Find closest inferior/equal possible speed */ + for (i = 0 ; i < 7 ; ++i) { + /* 2^(data_rate+2) */ + value = parent >> (i + 2); + + if (value <= speed) + break; + } + + /* If provided speed it lower than max divider, use max divider */ + if (i > 7) { + div = 7; + dev_warn_once(&spicc->pdev->dev, "unable to get close to speed %u\n", + speed); + } else + div = i; + + dev_dbg(&spicc->pdev->dev, "parent %lu, speed %u -> %lu (%u)\n", + parent, speed, value, div); + + conf &= ~SPICC_DATARATE_MASK; + conf |= FIELD_PREP(SPICC_DATARATE_MASK, div); + + return conf; +} + +static void meson_spicc_setup_xfer(struct meson_spicc_device *spicc, + struct spi_transfer *xfer) +{ + u32 conf, conf_orig; + + /* Read original configuration */ + conf = conf_orig = readl_relaxed(spicc->base + SPICC_CONREG); + + /* Select closest divider */ + conf = meson_spicc_setup_speed(spicc, conf, xfer->speed_hz); + + /* Setup word width */ + conf &= ~SPICC_BITLENGTH_MASK; + conf |= FIELD_PREP(SPICC_BITLENGTH_MASK, + (spicc->bytes_per_word << 3) - 1); + + /* Ignore if unchanged */ + if (conf != conf_orig) + writel_relaxed(conf, spicc->base + SPICC_CONREG); +} + +static int meson_spicc_transfer_one(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct meson_spicc_device *spicc = spi_master_get_devdata(master); + unsigned int burst_len; + u32 irq = 0; + + /* Store current transfer */ + spicc->xfer = xfer; + + /* Setup transfer parameters */ + spicc->tx_buf = (u8 *)xfer->tx_buf; + spicc->rx_buf = (u8 *)xfer->rx_buf; + spicc->xfer_remain = xfer->len; + + /* Pre-calculate word size */ + spicc->bytes_per_word = + DIV_ROUND_UP(spicc->xfer->bits_per_word, 8); + + /* Setup transfer parameters */ + meson_spicc_setup_xfer(spicc, xfer); + + burst_len = min_t(unsigned int, + spicc->xfer_remain / spicc->bytes_per_word, + SPICC_BURST_MAX); + + meson_spicc_setup_burst(spicc, burst_len); + + irq = meson_spicc_setup_rx_irq(spicc, irq); + + /* Start burst */ + writel_bits_relaxed(SPICC_XCH, SPICC_XCH, spicc->base + SPICC_CONREG); + + /* Enable interrupts */ + writel_relaxed(irq, spicc->base + SPICC_INTREG); + + return 1; +} + +static int meson_spicc_prepare_message(struct spi_master *master, + struct spi_message *message) +{ + struct meson_spicc_device *spicc = spi_master_get_devdata(master); + struct spi_device *spi = message->spi; + u32 conf = 0; + + /* Store current message */ + spicc->message = message; + + /* Enable Master */ + conf |= SPICC_ENABLE; + conf |= SPICC_MODE_MASTER; + + /* SMC = 0 */ + + /* Setup transfer mode */ + if (spi->mode & SPI_CPOL) + conf |= SPICC_POL; + else + conf &= ~SPICC_POL; + + if (spi->mode & SPI_CPHA) + conf |= SPICC_PHA; + else + conf &= ~SPICC_PHA; + + /* SSCTL = 0 */ + + if (spi->mode & SPI_CS_HIGH) + conf |= SPICC_SSPOL; + else + conf &= ~SPICC_SSPOL; + + if (spi->mode & SPI_READY) + conf |= FIELD_PREP(SPICC_DRCTL_MASK, SPICC_DRCTL_LOWLEVEL); + else + conf |= FIELD_PREP(SPICC_DRCTL_MASK, SPICC_DRCTL_IGNORE); + + /* Select CS */ + conf |= FIELD_PREP(SPICC_CS_MASK, spi->chip_select); + + /* Default Clock rate core/4 */ + + /* Default 8bit word */ + conf |= FIELD_PREP(SPICC_BITLENGTH_MASK, 8 - 1); + + writel_relaxed(conf, spicc->base + SPICC_CONREG); + + /* Setup no wait cycles by default */ + writel_relaxed(0, spicc->base + SPICC_PERIODREG); + + writel_bits_relaxed(BIT(24), BIT(24), spicc->base + SPICC_TESTREG); + + return 0; +} + +static int meson_spicc_unprepare_transfer(struct spi_master *master) +{ + struct meson_spicc_device *spicc = spi_master_get_devdata(master); + + /* Disable all IRQs */ + writel(0, spicc->base + SPICC_INTREG); + + /* Disable controller */ + writel_bits_relaxed(SPICC_ENABLE, 0, spicc->base + SPICC_CONREG); + + device_reset_optional(&spicc->pdev->dev); + + return 0; +} + +static int meson_spicc_setup(struct spi_device *spi) +{ + int ret = 0; + + if (!spi->controller_state) + spi->controller_state = spi_master_get_devdata(spi->master); + else if (gpio_is_valid(spi->cs_gpio)) + goto out_gpio; + else if (spi->cs_gpio == -ENOENT) + return 0; + + if (gpio_is_valid(spi->cs_gpio)) { + ret = gpio_request(spi->cs_gpio, dev_name(&spi->dev)); + if (ret) { + dev_err(&spi->dev, "failed to request cs gpio\n"); + return ret; + } + } + +out_gpio: + ret = gpio_direction_output(spi->cs_gpio, + !(spi->mode & SPI_CS_HIGH)); + + return ret; +} + +static void meson_spicc_cleanup(struct spi_device *spi) +{ + if (gpio_is_valid(spi->cs_gpio)) + gpio_free(spi->cs_gpio); + + spi->controller_state = NULL; +} + +static int meson_spicc_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct meson_spicc_device *spicc; + struct resource *res; + int ret, irq, rate; + + master = spi_alloc_master(&pdev->dev, sizeof(*spicc)); + if (!master) { + dev_err(&pdev->dev, "master allocation failed\n"); + return -ENOMEM; + } + spicc = spi_master_get_devdata(master); + spicc->master = master; + + spicc->pdev = pdev; + platform_set_drvdata(pdev, spicc); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + spicc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(spicc->base)) { + dev_err(&pdev->dev, "io resource mapping failed\n"); + ret = PTR_ERR(spicc->base); + goto out_master; + } + + /* Disable all IRQs */ + writel_relaxed(0, spicc->base + SPICC_INTREG); + + irq = platform_get_irq(pdev, 0); + ret = devm_request_irq(&pdev->dev, irq, meson_spicc_irq, + 0, NULL, spicc); + if (ret) { + dev_err(&pdev->dev, "irq request failed\n"); + goto out_master; + } + + spicc->core = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(spicc->core)) { + dev_err(&pdev->dev, "core clock request failed\n"); + ret = PTR_ERR(spicc->core); + goto out_master; + } + + ret = clk_prepare_enable(spicc->core); + if (ret) { + dev_err(&pdev->dev, "core clock enable failed\n"); + goto out_master; + } + rate = clk_get_rate(spicc->core); + + device_reset_optional(&pdev->dev); + + master->num_chipselect = 4; + master->dev.of_node = pdev->dev.of_node; + master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH; + master->bits_per_word_mask = SPI_BPW_MASK(32) | + SPI_BPW_MASK(24) | + SPI_BPW_MASK(16) | + SPI_BPW_MASK(8); + master->flags = (SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX); + master->min_speed_hz = rate >> 9; + master->setup = meson_spicc_setup; + master->cleanup = meson_spicc_cleanup; + master->prepare_message = meson_spicc_prepare_message; + master->unprepare_transfer_hardware = meson_spicc_unprepare_transfer; + master->transfer_one = meson_spicc_transfer_one; + + /* Setup max rate according to the Meson GX datasheet */ + if ((rate >> 2) > SPICC_MAX_FREQ) + master->max_speed_hz = SPICC_MAX_FREQ; + else + master->max_speed_hz = rate >> 2; + + ret = devm_spi_register_master(&pdev->dev, master); + if (!ret) + return 0; + + dev_err(&pdev->dev, "spi master registration failed\n"); + +out_master: + spi_master_put(master); + + return ret; +} + +static int meson_spicc_remove(struct platform_device *pdev) +{ + struct meson_spicc_device *spicc = platform_get_drvdata(pdev); + + /* Disable SPI */ + writel(0, spicc->base + SPICC_CONREG); + + clk_disable_unprepare(spicc->core); + + return 0; +} + +static const struct of_device_id meson_spicc_of_match[] = { + { .compatible = "amlogic,meson-gx-spicc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, meson_spicc_of_match); + +static struct platform_driver meson_spicc_driver = { + .probe = meson_spicc_probe, + .remove = meson_spicc_remove, + .driver = { + .name = "meson-spicc", + .of_match_table = of_match_ptr(meson_spicc_of_match), + }, +}; + +module_platform_driver(meson_spicc_driver); + +MODULE_DESCRIPTION("Meson SPI Communication Controller driver"); +MODULE_AUTHOR("Neil Armstrong "); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-mt65xx.c b/drivers/spi/spi-mt65xx.c index 278867a31950..86bf45667a04 100644 --- a/drivers/spi/spi-mt65xx.c +++ b/drivers/spi/spi-mt65xx.c @@ -35,11 +35,15 @@ #define SPI_CMD_REG 0x0018 #define SPI_STATUS0_REG 0x001c #define SPI_PAD_SEL_REG 0x0024 +#define SPI_CFG2_REG 0x0028 #define SPI_CFG0_SCK_HIGH_OFFSET 0 #define SPI_CFG0_SCK_LOW_OFFSET 8 #define SPI_CFG0_CS_HOLD_OFFSET 16 #define SPI_CFG0_CS_SETUP_OFFSET 24 +#define SPI_ADJUST_CFG0_SCK_LOW_OFFSET 16 +#define SPI_ADJUST_CFG0_CS_HOLD_OFFSET 0 +#define SPI_ADJUST_CFG0_CS_SETUP_OFFSET 16 #define SPI_CFG1_CS_IDLE_OFFSET 0 #define SPI_CFG1_PACKET_LOOP_OFFSET 8 @@ -55,6 +59,8 @@ #define SPI_CMD_RST BIT(2) #define SPI_CMD_PAUSE_EN BIT(4) #define SPI_CMD_DEASSERT BIT(5) +#define SPI_CMD_SAMPLE_SEL BIT(6) +#define SPI_CMD_CS_POL BIT(7) #define SPI_CMD_CPHA BIT(8) #define SPI_CMD_CPOL BIT(9) #define SPI_CMD_RX_DMA BIT(10) @@ -80,6 +86,8 @@ struct mtk_spi_compatible { bool need_pad_sel; /* Must explicitly send dummy Tx bytes to do Rx only transfer */ bool must_tx; + /* some IC design adjust cfg register to enhance time accuracy */ + bool enhance_timing; }; struct mtk_spi { @@ -96,6 +104,16 @@ struct mtk_spi { }; static const struct mtk_spi_compatible mtk_common_compat; + +static const struct mtk_spi_compatible mt2712_compat = { + .must_tx = true, +}; + +static const struct mtk_spi_compatible mt7622_compat = { + .must_tx = true, + .enhance_timing = true, +}; + static const struct mtk_spi_compatible mt8173_compat = { .need_pad_sel = true, .must_tx = true, @@ -108,15 +126,23 @@ static const struct mtk_spi_compatible mt8173_compat = { static const struct mtk_chip_config mtk_default_chip_info = { .rx_mlsb = 1, .tx_mlsb = 1, + .cs_pol = 0, + .sample_sel = 0, }; static const struct of_device_id mtk_spi_of_match[] = { { .compatible = "mediatek,mt2701-spi", .data = (void *)&mtk_common_compat, }, + { .compatible = "mediatek,mt2712-spi", + .data = (void *)&mt2712_compat, + }, { .compatible = "mediatek,mt6589-spi", .data = (void *)&mtk_common_compat, }, + { .compatible = "mediatek,mt7622-spi", + .data = (void *)&mt7622_compat, + }, { .compatible = "mediatek,mt8135-spi", .data = (void *)&mtk_common_compat, }, @@ -182,6 +208,17 @@ static int mtk_spi_prepare_message(struct spi_master *master, reg_val |= SPI_CMD_RX_ENDIAN; #endif + if (mdata->dev_comp->enhance_timing) { + if (chip_config->cs_pol) + reg_val |= SPI_CMD_CS_POL; + else + reg_val &= ~SPI_CMD_CS_POL; + if (chip_config->sample_sel) + reg_val |= SPI_CMD_SAMPLE_SEL; + else + reg_val &= ~SPI_CMD_SAMPLE_SEL; + } + /* set finish and pause interrupt always enable */ reg_val |= SPI_CMD_FINISH_IE | SPI_CMD_PAUSE_IE; @@ -233,11 +270,25 @@ static void mtk_spi_prepare_transfer(struct spi_master *master, sck_time = (div + 1) / 2; cs_time = sck_time * 2; - reg_val |= (((sck_time - 1) & 0xff) << SPI_CFG0_SCK_HIGH_OFFSET); - reg_val |= (((sck_time - 1) & 0xff) << SPI_CFG0_SCK_LOW_OFFSET); - reg_val |= (((cs_time - 1) & 0xff) << SPI_CFG0_CS_HOLD_OFFSET); - reg_val |= (((cs_time - 1) & 0xff) << SPI_CFG0_CS_SETUP_OFFSET); - writel(reg_val, mdata->base + SPI_CFG0_REG); + if (mdata->dev_comp->enhance_timing) { + reg_val |= (((sck_time - 1) & 0xffff) + << SPI_CFG0_SCK_HIGH_OFFSET); + reg_val |= (((sck_time - 1) & 0xffff) + << SPI_ADJUST_CFG0_SCK_LOW_OFFSET); + writel(reg_val, mdata->base + SPI_CFG2_REG); + reg_val |= (((cs_time - 1) & 0xffff) + << SPI_ADJUST_CFG0_CS_HOLD_OFFSET); + reg_val |= (((cs_time - 1) & 0xffff) + << SPI_ADJUST_CFG0_CS_SETUP_OFFSET); + writel(reg_val, mdata->base + SPI_CFG0_REG); + } else { + reg_val |= (((sck_time - 1) & 0xff) + << SPI_CFG0_SCK_HIGH_OFFSET); + reg_val |= (((sck_time - 1) & 0xff) << SPI_CFG0_SCK_LOW_OFFSET); + reg_val |= (((cs_time - 1) & 0xff) << SPI_CFG0_CS_HOLD_OFFSET); + reg_val |= (((cs_time - 1) & 0xff) << SPI_CFG0_CS_SETUP_OFFSET); + writel(reg_val, mdata->base + SPI_CFG0_REG); + } reg_val = readl(mdata->base + SPI_CFG1_REG); reg_val &= ~SPI_CFG1_CS_IDLE_MASK; diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index 7275223dbcd4..e048268d8ba2 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -1412,9 +1412,6 @@ static int omap2_mcspi_probe(struct platform_device *pdev) sprintf(mcspi->dma_channels[i].dma_tx_ch_name, "tx%d", i); } - if (status < 0) - goto free_master; - pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_set_autosuspend_delay(&pdev->dev, SPI_AUTOSUSPEND_TIMEOUT); pm_runtime_enable(&pdev->dev); diff --git a/include/linux/platform_data/spi-mt65xx.h b/include/linux/platform_data/spi-mt65xx.h index 54b04483976c..ba4e4bb70262 100644 --- a/include/linux/platform_data/spi-mt65xx.h +++ b/include/linux/platform_data/spi-mt65xx.h @@ -16,5 +16,7 @@ struct mtk_chip_config { u32 tx_mlsb; u32 rx_mlsb; + u32 cs_pol; + u32 sample_sel; }; #endif