mirror of
https://github.com/thead-yocto-mirror/meta-riscv
synced 2026-09-04 07:44:45 +02:00
linux: freedom-u540: Add the HiFive Unleashed drivers
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
This commit is contained in:
committed by
Khem Raj
parent
7a0d9f2e64
commit
7b8c06236f
@@ -0,0 +1,512 @@
|
||||
From f169ce2434ae12ada36877fbf21db5d26fde9496 Mon Sep 17 00:00:00 2001
|
||||
From: "Wesley W. Terpstra" <wesley@sifive.com>
|
||||
Date: Mon, 5 Feb 2018 17:42:32 -0800
|
||||
Subject: [PATCH 01/11] spi-sifive: support SiFive SPI controller in Quad-Mode
|
||||
|
||||
Signed-off-by: Palmer Dabbelt <palmer@sifive.com>
|
||||
---
|
||||
.../devicetree/bindings/spi/spi-sifive.txt | 29 ++
|
||||
drivers/spi/Kconfig | 7 +
|
||||
drivers/spi/Makefile | 1 +
|
||||
drivers/spi/spi-sifive.c | 423 +++++++++++++++++++++
|
||||
4 files changed, 460 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/spi/spi-sifive.txt
|
||||
create mode 100644 drivers/spi/spi-sifive.c
|
||||
|
||||
diff --git a/Documentation/devicetree/bindings/spi/spi-sifive.txt b/Documentation/devicetree/bindings/spi/spi-sifive.txt
|
||||
new file mode 100644
|
||||
index 00000000..94554324
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/spi/spi-sifive.txt
|
||||
@@ -0,0 +1,29 @@
|
||||
+SiFive SPI controller Device Tree Bindings
|
||||
+-------------------------------------------------
|
||||
+
|
||||
+Required properties:
|
||||
+- compatible : Should be "sifive,spi0"
|
||||
+- reg : Physical base address and size of SPI registers map
|
||||
+ A second (optional) range can indicate memory mapped flash
|
||||
+- interrupts : Must contain one entry
|
||||
+- interrupt-parent : Must be core interrupt controller
|
||||
+- clocks : Must reference the frequency given to the controller
|
||||
+- #address-cells : Must be '1', indicating which CS to use
|
||||
+- #size-cells : Must be '0'
|
||||
+
|
||||
+Optional properties:
|
||||
+- sifive,buffer-size : Depth of hardware queues; defaults to 8
|
||||
+- sifive,bits-per-word : Maximum bits per word; defaults to 8
|
||||
+
|
||||
+Example:
|
||||
+ spi: spi@10040000 {
|
||||
+ compatible = "sifive,spi0";
|
||||
+ reg = <0x0 0x10040000 0x0 0x1000 0x0 0x20000000 0x0 0x10000000>;
|
||||
+ interrupt-parent = <&plic>;
|
||||
+ interrupts = <51>;
|
||||
+ clocks = <&tlclk>;
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+ sifive,buffer-size = <8>;
|
||||
+ sifive,bits-per-word = <8>;
|
||||
+ };
|
||||
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
|
||||
index 671d0783..b3aa058c 100644
|
||||
--- a/drivers/spi/Kconfig
|
||||
+++ b/drivers/spi/Kconfig
|
||||
@@ -749,6 +749,13 @@ config SPI_ZYNQMP_GQSPI
|
||||
help
|
||||
Enables Xilinx GQSPI controller driver for Zynq UltraScale+ MPSoC.
|
||||
|
||||
+config SPI_SIFIVE
|
||||
+ tristate "SiFive SPI controller"
|
||||
+ depends on HAS_IOMEM
|
||||
+ select SPI_BITBANG
|
||||
+ help
|
||||
+ This exposes the SPI controller IP from SiFive.
|
||||
+
|
||||
#
|
||||
# Add new SPI master controllers in alphabetical order above this line
|
||||
#
|
||||
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
|
||||
index a90d5597..e22ffa78 100644
|
||||
--- a/drivers/spi/Makefile
|
||||
+++ b/drivers/spi/Makefile
|
||||
@@ -107,6 +107,7 @@ obj-$(CONFIG_SPI_XILINX) += spi-xilinx.o
|
||||
obj-$(CONFIG_SPI_XLP) += spi-xlp.o
|
||||
obj-$(CONFIG_SPI_XTENSA_XTFPGA) += spi-xtensa-xtfpga.o
|
||||
obj-$(CONFIG_SPI_ZYNQMP_GQSPI) += spi-zynqmp-gqspi.o
|
||||
+obj-$(CONFIG_SPI_SIFIVE) += spi-sifive.o
|
||||
|
||||
# SPI slave protocol handlers
|
||||
obj-$(CONFIG_SPI_SLAVE_TIME) += spi-slave-time.o
|
||||
diff --git a/drivers/spi/spi-sifive.c b/drivers/spi/spi-sifive.c
|
||||
new file mode 100644
|
||||
index 00000000..208566a9
|
||||
--- /dev/null
|
||||
+++ b/drivers/spi/spi-sifive.c
|
||||
@@ -0,0 +1,423 @@
|
||||
+/*
|
||||
+ * SiFive SPI controller driver (master mode only)
|
||||
+ *
|
||||
+ * Author: SiFive, Inc.
|
||||
+ * sifive@sifive.com
|
||||
+ *
|
||||
+ * 2018 (c) SiFive Software, Inc.
|
||||
+
|
||||
+ * This program is free software; you can redistribute it and/or modify
|
||||
+ * it under the terms of the GNU General Public License version 2 as
|
||||
+ * published by the Free Software Foundation.
|
||||
+ */
|
||||
+
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/interrupt.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/spi/spi.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/log2.h>
|
||||
+
|
||||
+#define SIFIVE_SPI_MAX_CS 32
|
||||
+
|
||||
+#define SIFIVE_SPI_NAME "sifive_spi"
|
||||
+
|
||||
+#define SIFIVE_SPI_DEFAULT_DEPTH 8
|
||||
+#define SIFIVE_SPI_DEFAULT_BITS 8
|
||||
+
|
||||
+#define XSPI_SCDR_OFFSET 0x000 /* Serial Clock Divisor Register */
|
||||
+#define XSPI_SCD_SCALE_MASK 0xFFF
|
||||
+
|
||||
+#define XSPI_SCMR_OFFSET 0x004 /* Serial Clock Mode Register */
|
||||
+#define XSPI_SCM_CPHA 1
|
||||
+#define XSPI_SCM_CPOL 2
|
||||
+#define XSPI_SCM_MODE_MASK (XSPI_SCM_CPHA | XSPI_SCM_CPOL)
|
||||
+
|
||||
+#define XSPI_CSIDR_OFFSET 0x010
|
||||
+#define XSPI_CSDR_OFFSET 0x014
|
||||
+#define XSPI_CSMR_OFFSET 0x018
|
||||
+#define XSPI_CSM_MODE_AUTO 0
|
||||
+#define XSPI_CSM_MODE_HOLD 2
|
||||
+#define XSPI_CSM_MODE_OFF 3
|
||||
+
|
||||
+#define XSPI_DC0R_OFFSET 0x028
|
||||
+#define XSPI_CS_TO_SCK_MASK 0xFF
|
||||
+#define XSPI_SCK_TO_CS_MASK (0xFF << 16)
|
||||
+#define XSPI_DC1R_OFFSET 0x02C
|
||||
+#define XSPI_MIN_CS_IATIME_MASK 0xFF
|
||||
+#define XSPI_MAX_IF_DELAY_MASK (0xFF << 16)
|
||||
+
|
||||
+#define XSPI_FFR_OFFSET 0x040
|
||||
+#define XSPI_FF_SINGLE 0
|
||||
+#define XSPI_FF_DUAL 1
|
||||
+#define XSPI_FF_QUAD 2
|
||||
+#define XSPI_FF_SPI_MASK 0x3
|
||||
+#define XSPI_FF_LSB_FIRST 4
|
||||
+#define XSPI_FF_TX_DIR 8
|
||||
+#define XSPI_FF_BPF_MASK (0xFF << 16)
|
||||
+
|
||||
+#define XSPI_TXDR_OFFSET 0x048 /* Data Transmit Register */
|
||||
+#define XSPI_TXD_FIFO_FULL (8U << 28)
|
||||
+#define XSPI_RXDR_OFFSET 0x04C /* Data Receive Register */
|
||||
+#define XSPI_RXD_FIFO_EMPTY (8U << 28)
|
||||
+#define XSPI_DATA_MASK 0xFF
|
||||
+#define XSPI_DATA_SHIFT 8
|
||||
+
|
||||
+#define XSPI_TXWMR_OFFSET 0x050 /* TX FIFO Watermark Register */
|
||||
+#define XSPI_RXWMR_OFFSET 0x054 /* RX FIFO Watermark Register */
|
||||
+
|
||||
+#define XSPI_FCTRL_OFFSET 0x60
|
||||
+
|
||||
+#define XSPI_IPR_OFFSET 0x074 /* Interrupt Pendings Register */
|
||||
+#define XSPI_IER_OFFSET 0x070 /* Interrupt Enable Register */
|
||||
+#define XSPI_TXWM_INTR 0x1
|
||||
+#define XSPI_RXWM_INTR 0x2
|
||||
+
|
||||
+struct sifive_spi {
|
||||
+ void __iomem *regs; /* virt. address of the control registers */
|
||||
+ struct clk *clk; /* bus clock */
|
||||
+ int irq; /* watermark irq */
|
||||
+ int buffer_size; /* buffer size in words */
|
||||
+ u32 cs_inactive; /* Level of the CS pins when inactive*/
|
||||
+ struct completion done; /* Wake-up from interrupt */
|
||||
+};
|
||||
+
|
||||
+static void sifive_spi_write(struct sifive_spi *spi, int offset, u32 value)
|
||||
+{
|
||||
+ iowrite32(value, spi->regs + offset);
|
||||
+}
|
||||
+
|
||||
+static u32 sifive_spi_read(struct sifive_spi *spi, int offset)
|
||||
+{
|
||||
+ return ioread32(spi->regs + offset);
|
||||
+}
|
||||
+
|
||||
+static void sifive_spi_init(struct sifive_spi *spi)
|
||||
+{
|
||||
+ /* Watermark interrupts are disabled by default */
|
||||
+ sifive_spi_write(spi, XSPI_IER_OFFSET, 0);
|
||||
+
|
||||
+ /* Default watermark FIFO threshold values */
|
||||
+ sifive_spi_write(spi, XSPI_TXWMR_OFFSET, 1);
|
||||
+ sifive_spi_write(spi, XSPI_RXWMR_OFFSET, 0);
|
||||
+
|
||||
+ /* Set CS/SCK Delays and Inactive Time to defaults */
|
||||
+
|
||||
+ /* Exit specialized memory-mapped SPI flash mode */
|
||||
+ sifive_spi_write(spi, XSPI_FCTRL_OFFSET, 0);
|
||||
+}
|
||||
+
|
||||
+static void sifive_spi_prep_device(struct sifive_spi *spi, struct spi_device *device)
|
||||
+{
|
||||
+ u32 cr;
|
||||
+
|
||||
+ /* Update the chip select polarity */
|
||||
+ if (device->mode & SPI_CS_HIGH)
|
||||
+ spi->cs_inactive &= ~BIT(device->chip_select);
|
||||
+ else
|
||||
+ spi->cs_inactive |= BIT(device->chip_select);
|
||||
+ sifive_spi_write(spi, XSPI_CSDR_OFFSET, spi->cs_inactive);
|
||||
+
|
||||
+ /* Select the correct device */
|
||||
+ sifive_spi_write(spi, XSPI_CSIDR_OFFSET, device->chip_select);
|
||||
+
|
||||
+ /* Switch clock mode bits */
|
||||
+ cr = sifive_spi_read(spi, XSPI_SCMR_OFFSET) & ~XSPI_SCM_MODE_MASK;
|
||||
+ if (device->mode & SPI_CPHA)
|
||||
+ cr |= XSPI_SCM_CPHA;
|
||||
+ if (device->mode & SPI_CPOL)
|
||||
+ cr |= XSPI_SCM_CPOL;
|
||||
+ sifive_spi_write(spi, XSPI_SCMR_OFFSET, cr);
|
||||
+}
|
||||
+
|
||||
+static int sifive_spi_prep_transfer(struct sifive_spi *spi, struct spi_device *device, struct spi_transfer *t)
|
||||
+{
|
||||
+ u32 hz, scale, cr;
|
||||
+ int mode;
|
||||
+
|
||||
+ /* Calculate and program the clock rate */
|
||||
+ hz = t->speed_hz ? t->speed_hz : device->max_speed_hz;
|
||||
+ scale = (DIV_ROUND_UP(clk_get_rate(spi->clk) >> 1, hz) - 1) & XSPI_SCD_SCALE_MASK;
|
||||
+ sifive_spi_write(spi, XSPI_SCDR_OFFSET, scale);
|
||||
+
|
||||
+ /* Modify the SPI protocol mode */
|
||||
+ cr = sifive_spi_read(spi, XSPI_FFR_OFFSET);
|
||||
+
|
||||
+ /* LSB first? */
|
||||
+ cr &= ~XSPI_FF_LSB_FIRST;
|
||||
+ if (device->mode & SPI_LSB_FIRST)
|
||||
+ cr |= XSPI_FF_LSB_FIRST;
|
||||
+
|
||||
+ /* SINGLE/DUAL/QUAD? */
|
||||
+ mode = max((int)t->rx_nbits, (int)t->tx_nbits);
|
||||
+ cr &= ~XSPI_FF_SPI_MASK;
|
||||
+ switch (mode) {
|
||||
+ case SPI_NBITS_QUAD: cr |= XSPI_FF_QUAD; break;
|
||||
+ case SPI_NBITS_DUAL: cr |= XSPI_FF_DUAL; break;
|
||||
+ default: cr |= XSPI_FF_SINGLE; break;
|
||||
+ }
|
||||
+
|
||||
+ /* SPI direction */
|
||||
+ cr &= ~XSPI_FF_TX_DIR;
|
||||
+ if (!t->rx_buf)
|
||||
+ cr |= XSPI_FF_TX_DIR;
|
||||
+
|
||||
+ sifive_spi_write(spi, XSPI_FFR_OFFSET, cr);
|
||||
+
|
||||
+ /* We will want to poll if the time we need to wait is less than the context switching time.
|
||||
+ * Let's call that threshold 5us. The operation will take:
|
||||
+ * (8/mode) * buffer_size / hz <= 5 * 10^-6
|
||||
+ * 1600000 * buffer_size <= hz * mode
|
||||
+ */
|
||||
+ return 1600000 * spi->buffer_size <= hz * mode;
|
||||
+}
|
||||
+
|
||||
+static void sifive_spi_tx(struct sifive_spi *spi, const u8* tx_ptr)
|
||||
+{
|
||||
+ BUG_ON((sifive_spi_read(spi, XSPI_TXDR_OFFSET) & XSPI_TXD_FIFO_FULL) != 0);
|
||||
+ sifive_spi_write(spi, XSPI_TXDR_OFFSET, *tx_ptr & XSPI_DATA_MASK);
|
||||
+}
|
||||
+
|
||||
+static void sifive_spi_rx(struct sifive_spi *spi, u8* rx_ptr)
|
||||
+{
|
||||
+ u32 data = sifive_spi_read(spi, XSPI_RXDR_OFFSET);
|
||||
+ BUG_ON((data & XSPI_RXD_FIFO_EMPTY) != 0);
|
||||
+ *rx_ptr = data & XSPI_DATA_MASK;
|
||||
+}
|
||||
+
|
||||
+static void sifive_spi_wait(struct sifive_spi *spi, int bit, int poll)
|
||||
+{
|
||||
+ if (poll) {
|
||||
+ u32 cr;
|
||||
+ do cr = sifive_spi_read(spi, XSPI_IPR_OFFSET);
|
||||
+ while (!(cr & bit));
|
||||
+ } else {
|
||||
+ reinit_completion(&spi->done);
|
||||
+ sifive_spi_write(spi, XSPI_IER_OFFSET, bit);
|
||||
+ wait_for_completion(&spi->done);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static void sifive_spi_execute(struct sifive_spi *spi, struct spi_transfer *t, int poll)
|
||||
+{
|
||||
+ int remaining_words = t->len;
|
||||
+ const u8* tx_ptr = t->tx_buf;
|
||||
+ u8* rx_ptr = t->rx_buf;
|
||||
+
|
||||
+ while (remaining_words) {
|
||||
+ int n_words, tx_words, rx_words;
|
||||
+ n_words = min(remaining_words, spi->buffer_size);
|
||||
+
|
||||
+ /* Enqueue n_words for transmission */
|
||||
+ for (tx_words = 0; tx_words < n_words; ++tx_words)
|
||||
+ sifive_spi_tx(spi, tx_ptr++);
|
||||
+
|
||||
+ if (rx_ptr) {
|
||||
+ /* Wait for transmission + reception to complete */
|
||||
+ sifive_spi_write(spi, XSPI_RXWMR_OFFSET, n_words-1);
|
||||
+ sifive_spi_wait(spi, XSPI_RXWM_INTR, poll);
|
||||
+
|
||||
+ /* Read out all the data from the RX FIFO */
|
||||
+ for (rx_words = 0; rx_words < n_words; ++rx_words)
|
||||
+ sifive_spi_rx(spi, rx_ptr++);
|
||||
+ } else {
|
||||
+ /* Wait for transmission to complete */
|
||||
+ sifive_spi_wait(spi, XSPI_TXWM_INTR, poll);
|
||||
+ }
|
||||
+
|
||||
+ remaining_words -= n_words;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static int sifive_spi_transfer_one(struct spi_master *master, struct spi_device *device, struct spi_transfer *t)
|
||||
+{
|
||||
+ struct sifive_spi *spi = spi_master_get_devdata(master);
|
||||
+ int poll;
|
||||
+
|
||||
+ sifive_spi_prep_device(spi, device);
|
||||
+ poll = sifive_spi_prep_transfer(spi, device, t);
|
||||
+ sifive_spi_execute(spi, t, poll);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void sifive_spi_set_cs(struct spi_device *device, bool is_high)
|
||||
+{
|
||||
+ struct sifive_spi *spi = spi_master_get_devdata(device->master);
|
||||
+
|
||||
+ /* Reverse polarity is handled by SCMR/CPOL. Not inverted CS. */
|
||||
+ if (device->mode & SPI_CS_HIGH)
|
||||
+ is_high = !is_high;
|
||||
+
|
||||
+ sifive_spi_write(spi, XSPI_CSMR_OFFSET, is_high ? XSPI_CSM_MODE_AUTO : XSPI_CSM_MODE_HOLD);
|
||||
+}
|
||||
+
|
||||
+static irqreturn_t sifive_spi_irq(int irq, void *dev_id)
|
||||
+{
|
||||
+ struct sifive_spi *spi = dev_id;
|
||||
+ u32 ip;
|
||||
+
|
||||
+ ip = sifive_spi_read(spi, XSPI_IPR_OFFSET) & (XSPI_TXWM_INTR | XSPI_RXWM_INTR);
|
||||
+ if (ip != 0) {
|
||||
+ /* Disable interrupts until next transfer */
|
||||
+ sifive_spi_write(spi, XSPI_IER_OFFSET, 0);
|
||||
+ complete(&spi->done);
|
||||
+ return IRQ_HANDLED;
|
||||
+ }
|
||||
+
|
||||
+ return IRQ_NONE;
|
||||
+}
|
||||
+
|
||||
+static int sifive_spi_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct sifive_spi *spi;
|
||||
+ struct resource *res;
|
||||
+ int ret, num_cs;
|
||||
+ u32 cs_bits, buffer_size, bits_per_word;
|
||||
+ struct spi_master *master;
|
||||
+
|
||||
+ master = spi_alloc_master(&pdev->dev, sizeof(struct sifive_spi));
|
||||
+ if (!master) {
|
||||
+ dev_err(&pdev->dev, "out of memory\n");
|
||||
+ return -ENOMEM;
|
||||
+ }
|
||||
+
|
||||
+ spi = spi_master_get_devdata(master);
|
||||
+ init_completion(&spi->done);
|
||||
+ platform_set_drvdata(pdev, master);
|
||||
+
|
||||
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ spi->regs = devm_ioremap_resource(&pdev->dev, res);
|
||||
+ if (IS_ERR(spi->regs)) {
|
||||
+ dev_err(&pdev->dev, "Unable to map IO resources\n");
|
||||
+ ret = PTR_ERR(spi->regs);
|
||||
+ goto put_master;
|
||||
+ }
|
||||
+
|
||||
+ spi->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
+ if (IS_ERR(spi->clk)) {
|
||||
+ dev_err(&pdev->dev, "Unable to find bus clock\n");
|
||||
+ ret = PTR_ERR(spi->clk);
|
||||
+ goto put_master;
|
||||
+ }
|
||||
+
|
||||
+ spi->irq = platform_get_irq(pdev, 0);
|
||||
+ if (spi->irq < 0) {
|
||||
+ dev_err(&pdev->dev, "Unable to find interrupt\n");
|
||||
+ ret = spi->irq;
|
||||
+ goto put_master;
|
||||
+ }
|
||||
+
|
||||
+ /* Optional parameters */
|
||||
+ ret = of_property_read_u32(pdev->dev.of_node, "sifive,buffer-size", &buffer_size);
|
||||
+ if (ret < 0)
|
||||
+ spi->buffer_size = SIFIVE_SPI_DEFAULT_DEPTH;
|
||||
+ else
|
||||
+ spi->buffer_size = buffer_size;
|
||||
+
|
||||
+ ret = of_property_read_u32(pdev->dev.of_node, "sifive,bits-per-word", &bits_per_word);
|
||||
+ if (ret < 0)
|
||||
+ bits_per_word = SIFIVE_SPI_DEFAULT_BITS;
|
||||
+
|
||||
+ /* Spin up the bus clock before hitting registers */
|
||||
+ ret = clk_prepare_enable(spi->clk);
|
||||
+ if (ret) {
|
||||
+ dev_err(&pdev->dev, "Unable to enable bus clock\n");
|
||||
+ goto put_master;
|
||||
+ }
|
||||
+
|
||||
+ /* probe the number of CS lines */
|
||||
+ spi->cs_inactive = sifive_spi_read(spi, XSPI_CSDR_OFFSET);
|
||||
+ sifive_spi_write(spi, XSPI_CSDR_OFFSET, 0xffffffffU);
|
||||
+ cs_bits = sifive_spi_read(spi, XSPI_CSDR_OFFSET);
|
||||
+ sifive_spi_write(spi, XSPI_CSDR_OFFSET, spi->cs_inactive);
|
||||
+ if (!cs_bits) {
|
||||
+ dev_err(&pdev->dev, "Could not auto probe CS lines\n");
|
||||
+ ret = -EINVAL;
|
||||
+ goto put_master;
|
||||
+ }
|
||||
+
|
||||
+ num_cs = ilog2(cs_bits) + 1;
|
||||
+ if (num_cs > SIFIVE_SPI_MAX_CS) {
|
||||
+ dev_err(&pdev->dev, "Invalid number of spi slaves\n");
|
||||
+ ret = -EINVAL;
|
||||
+ goto put_master;
|
||||
+ }
|
||||
+
|
||||
+ /* Define our master */
|
||||
+ master->bus_num = pdev->id;
|
||||
+ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST | SPI_CS_HIGH |
|
||||
+ SPI_TX_DUAL | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD;
|
||||
+ master->flags = SPI_CONTROLLER_MUST_TX | SPI_MASTER_GPIO_SS;
|
||||
+ master->dev.of_node = pdev->dev.of_node;
|
||||
+ master->bits_per_word_mask = SPI_BPW_MASK(bits_per_word);
|
||||
+ master->num_chipselect = num_cs;
|
||||
+ master->transfer_one = sifive_spi_transfer_one;
|
||||
+ master->set_cs = sifive_spi_set_cs;
|
||||
+
|
||||
+ /* If mmc_spi sees a dma_mask, it starts using dma mapped buffers.
|
||||
+ * Probably it should rely on the SPI core auto mapping instead.
|
||||
+ */
|
||||
+ pdev->dev.dma_mask = 0;
|
||||
+
|
||||
+ /* Configure the SPI master hardware */
|
||||
+ sifive_spi_init(spi);
|
||||
+
|
||||
+ /* Register for SPI Interrupt */
|
||||
+ ret = devm_request_irq(&pdev->dev, spi->irq, sifive_spi_irq, 0,
|
||||
+ dev_name(&pdev->dev), spi);
|
||||
+ if (ret) {
|
||||
+ dev_err(&pdev->dev, "Unable to bind to interrupt\n");
|
||||
+ goto put_master;
|
||||
+ }
|
||||
+
|
||||
+ dev_info(&pdev->dev, "mapped; irq=%d, cs=%d\n",
|
||||
+ spi->irq, master->num_chipselect);
|
||||
+
|
||||
+ ret = devm_spi_register_master(&pdev->dev, master);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(&pdev->dev, "spi_register_master failed\n");
|
||||
+ goto put_master;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+put_master:
|
||||
+ spi_master_put(master);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int sifive_spi_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct spi_master *master = platform_get_drvdata(pdev);
|
||||
+ struct sifive_spi *spi = spi_master_get_devdata(master);
|
||||
+
|
||||
+ /* Disable all the interrupts just in case */
|
||||
+ sifive_spi_write(spi, XSPI_IER_OFFSET, 0);
|
||||
+ spi_master_put(master);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id sifive_spi_of_match[] = {
|
||||
+ { .compatible = "sifive,spi0", },
|
||||
+ {}
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, sifive_spi_of_match);
|
||||
+
|
||||
+static struct platform_driver sifive_spi_driver = {
|
||||
+ .probe = sifive_spi_probe,
|
||||
+ .remove = sifive_spi_remove,
|
||||
+ .driver = {
|
||||
+ .name = SIFIVE_SPI_NAME,
|
||||
+ .of_match_table = sifive_spi_of_match,
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(sifive_spi_driver);
|
||||
+
|
||||
+MODULE_AUTHOR("SiFive, Inc. <sifive@sifive.com>");
|
||||
+MODULE_DESCRIPTION("SiFive SPI driver");
|
||||
+MODULE_LICENSE("GPL");
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
From dc4e19c27fceaf46ec61da8130bddb43ce940764 Mon Sep 17 00:00:00 2001
|
||||
From: "Wesley W. Terpstra" <wesley@sifive.com>
|
||||
Date: Mon, 5 Feb 2018 17:44:19 -0800
|
||||
Subject: [PATCH 02/11] spi-nor: add support for is25wp{32,64,128,256}
|
||||
|
||||
Signed-off-by: Palmer Dabbelt <palmer@sifive.com>
|
||||
---
|
||||
drivers/mtd/spi-nor/spi-nor.c | 47 ++++++++++++++++++++++++++++++++++++++++++-
|
||||
include/linux/mtd/spi-nor.h | 2 ++
|
||||
2 files changed, 48 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
|
||||
index f028277f..d39a7261 100644
|
||||
--- a/drivers/mtd/spi-nor/spi-nor.c
|
||||
+++ b/drivers/mtd/spi-nor/spi-nor.c
|
||||
@@ -1072,6 +1072,9 @@ static const struct flash_info spi_nor_ids[] = {
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "is25wp128", INFO(0x9d7018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
+ { "is25wp256d", INFO(0x9d7019, 0, 32 * 1024, 1024,
|
||||
+ SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES)
|
||||
+ },
|
||||
|
||||
/* Macronix */
|
||||
{ "mx25l512e", INFO(0xc22010, 0, 64 * 1024, 1, SECT_4K) },
|
||||
@@ -1515,6 +1518,45 @@ static int macronix_quad_enable(struct spi_nor *nor)
|
||||
return 0;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * issi_unlock() - clear BP[0123] write-protection.
|
||||
+ * @nor: pointer to a 'struct spi_nor'
|
||||
+ *
|
||||
+ * Bits [2345] of the Status Register are BP[0123].
|
||||
+ * ISSI chips use a different block protection scheme than other chips.
|
||||
+ * Just disable the write-protect unilaterally.
|
||||
+ *
|
||||
+ * Return: 0 on success, -errno otherwise.
|
||||
+ */
|
||||
+static int issi_unlock(struct spi_nor *nor)
|
||||
+{
|
||||
+ int ret, val;
|
||||
+ u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3;
|
||||
+
|
||||
+ val = read_sr(nor);
|
||||
+ if (val < 0)
|
||||
+ return val;
|
||||
+ if (!(val & mask))
|
||||
+ return 0;
|
||||
+
|
||||
+ write_enable(nor);
|
||||
+
|
||||
+ write_sr(nor, val & ~mask);
|
||||
+
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = read_sr(nor);
|
||||
+ if (ret > 0 && !(ret & mask)) {
|
||||
+ dev_info(nor->dev, "ISSI Block Protection Bits cleared\n");
|
||||
+ return 0;
|
||||
+ } else {
|
||||
+ dev_err(nor->dev, "ISSI Block Protection Bits not cleared\n");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Write status Register and configuration register with 2 bytes
|
||||
* The first byte will be written to the status register, while the
|
||||
@@ -2747,6 +2789,9 @@ static int spi_nor_init(struct spi_nor *nor)
|
||||
spi_nor_wait_till_ready(nor);
|
||||
}
|
||||
|
||||
+ if (JEDEC_MFR(nor->info) == SNOR_MFR_ISSI)
|
||||
+ issi_unlock(nor);
|
||||
+
|
||||
if (nor->quad_enable) {
|
||||
err = nor->quad_enable(nor);
|
||||
if (err) {
|
||||
@@ -2940,7 +2985,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- if (nor->addr_width) {
|
||||
+ if (nor->addr_width && JEDEC_MFR(info) != SNOR_MFR_ISSI) {
|
||||
/* already configured from SFDP */
|
||||
} else if (info->addr_width) {
|
||||
nor->addr_width = info->addr_width;
|
||||
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
|
||||
index c922e97f..1a3d07cf 100644
|
||||
--- a/include/linux/mtd/spi-nor.h
|
||||
+++ b/include/linux/mtd/spi-nor.h
|
||||
@@ -23,6 +23,7 @@
|
||||
#define SNOR_MFR_ATMEL CFI_MFR_ATMEL
|
||||
#define SNOR_MFR_GIGADEVICE 0xc8
|
||||
#define SNOR_MFR_INTEL CFI_MFR_INTEL
|
||||
+#define SNOR_MFR_ISSI 0x9d
|
||||
#define SNOR_MFR_MICRON CFI_MFR_ST /* ST Micro <--> Micron */
|
||||
#define SNOR_MFR_MACRONIX CFI_MFR_MACRONIX
|
||||
#define SNOR_MFR_SPANSION CFI_MFR_AMD
|
||||
@@ -121,6 +122,7 @@
|
||||
#define SR_BP0 BIT(2) /* Block protect 0 */
|
||||
#define SR_BP1 BIT(3) /* Block protect 1 */
|
||||
#define SR_BP2 BIT(4) /* Block protect 2 */
|
||||
+#define SR_BP3 BIT(5) /* Block protect 3 (on ISSI chips) */
|
||||
#define SR_TB BIT(5) /* Top/Bottom protect */
|
||||
#define SR_SRWD BIT(7) /* SR write protect */
|
||||
/* Spansion/Cypress specific status bits */
|
||||
--
|
||||
2.7.4
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,235 @@
|
||||
From c61e4295c2b2e29bbfe4af4795e2ca862c5de8e3 Mon Sep 17 00:00:00 2001
|
||||
From: "Wesley W. Terpstra" <wesley@sifive.com>
|
||||
Date: Tue, 6 Feb 2018 11:03:07 -0800
|
||||
Subject: [PATCH 04/11] gemgxl-mgmt: implement clock switch for GEM tx_clk
|
||||
|
||||
Signed-off-by: Palmer Dabbelt <palmer@sifive.com>
|
||||
---
|
||||
.../bindings/clock/sifive,gemgxl-mgmt.txt | 26 +++++
|
||||
drivers/clk/Kconfig | 1 +
|
||||
drivers/clk/Makefile | 1 +
|
||||
drivers/clk/sifive/Kconfig | 9 ++
|
||||
drivers/clk/sifive/Makefile | 2 +
|
||||
drivers/clk/sifive/gemgxl-mgmt.c | 129 +++++++++++++++++++++
|
||||
6 files changed, 168 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/clock/sifive,gemgxl-mgmt.txt
|
||||
create mode 100644 drivers/clk/sifive/Kconfig
|
||||
create mode 100644 drivers/clk/sifive/Makefile
|
||||
create mode 100644 drivers/clk/sifive/gemgxl-mgmt.c
|
||||
|
||||
diff --git a/Documentation/devicetree/bindings/clock/sifive,gemgxl-mgmt.txt b/Documentation/devicetree/bindings/clock/sifive,gemgxl-mgmt.txt
|
||||
new file mode 100644
|
||||
index 00000000..349489e3
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/clock/sifive,gemgxl-mgmt.txt
|
||||
@@ -0,0 +1,26 @@
|
||||
+TX clock switch for GEMGXL in U540 SoCs
|
||||
+
|
||||
+This binding uses the common clock binding:
|
||||
+ Documentation/devicetree/bindings/clock/clock-bindings.txt
|
||||
+
|
||||
+The U54 includes a clock mux to control the ethernet TX frequenecy. It
|
||||
+switches between the local TX clock (125MHz) and PHY TX clocks. This is
|
||||
+necessary to toggle between 1Gb and 100/10Mb speeds.
|
||||
+
|
||||
+Required properties:
|
||||
+- compatible: Should be "sifive,cadencegemgxlmgmt0"
|
||||
+- #clock-cells: Should be <0>
|
||||
+- reg: Specifies base physical address and size of the registers
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+ mgmt: cadence-gemgxl-mgmt@100a00000 {
|
||||
+ compatible = "sifive,cadencegemgxlmgmt0";
|
||||
+ #clock-cells = <0>;
|
||||
+ reg = <0x0 0x100a0000 0x0 0x1000>;
|
||||
+ };
|
||||
+
|
||||
+ ethernet@10090000 {
|
||||
+ ...
|
||||
+ clocks = <&mgmt>; /* TX clock */
|
||||
+ };
|
||||
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
|
||||
index 292056bb..d4cca333 100644
|
||||
--- a/drivers/clk/Kconfig
|
||||
+++ b/drivers/clk/Kconfig
|
||||
@@ -299,5 +299,6 @@ source "drivers/clk/sunxi-ng/Kconfig"
|
||||
source "drivers/clk/tegra/Kconfig"
|
||||
source "drivers/clk/ti/Kconfig"
|
||||
source "drivers/clk/uniphier/Kconfig"
|
||||
+source "drivers/clk/sifive/Kconfig"
|
||||
|
||||
endmenu
|
||||
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
|
||||
index a84c5573..858bdb9b 100644
|
||||
--- a/drivers/clk/Makefile
|
||||
+++ b/drivers/clk/Makefile
|
||||
@@ -90,6 +90,7 @@ obj-$(CONFIG_COMMON_CLK_QCOM) += qcom/
|
||||
obj-y += renesas/
|
||||
obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
|
||||
obj-$(CONFIG_COMMON_CLK_SAMSUNG) += samsung/
|
||||
+obj-y += sifive/
|
||||
obj-$(CONFIG_ARCH_SIRF) += sirf/
|
||||
obj-$(CONFIG_ARCH_SOCFPGA) += socfpga/
|
||||
obj-$(CONFIG_PLAT_SPEAR) += spear/
|
||||
diff --git a/drivers/clk/sifive/Kconfig b/drivers/clk/sifive/Kconfig
|
||||
new file mode 100644
|
||||
index 00000000..284bffb1
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/sifive/Kconfig
|
||||
@@ -0,0 +1,9 @@
|
||||
+config CLK_U54_PRCI
|
||||
+ bool "PRCI driver for U54 SoCs"
|
||||
+ ---help---
|
||||
+ Supports Power Reset Clock interface found in U540 SoCs
|
||||
+
|
||||
+config CLK_GEMGXL_MGMT
|
||||
+ bool "TX clock switch for GEMGXL in U540 SoCs"
|
||||
+ ---help---
|
||||
+ Supports clock muxing between 10/100Mbit and 1Gbit TX clock on U540 SoCs
|
||||
diff --git a/drivers/clk/sifive/Makefile b/drivers/clk/sifive/Makefile
|
||||
new file mode 100644
|
||||
index 00000000..7784d2ee
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/sifive/Makefile
|
||||
@@ -0,0 +1,2 @@
|
||||
+obj-$(CONFIG_CLK_U54_PRCI) += u54-prci.o
|
||||
+obj-$(CONFIG_CLK_GEMGXL_MGMT) += gemgxl-mgmt.o
|
||||
diff --git a/drivers/clk/sifive/gemgxl-mgmt.c b/drivers/clk/sifive/gemgxl-mgmt.c
|
||||
new file mode 100644
|
||||
index 00000000..00b07580
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/sifive/gemgxl-mgmt.c
|
||||
@@ -0,0 +1,129 @@
|
||||
+/*
|
||||
+ * This program is free software; you can redistribute it and/or modify
|
||||
+ * it under the terms of the GNU General Public License version 2 as
|
||||
+ * published by the Free Software Foundation.
|
||||
+ *
|
||||
+ * This program is distributed in the hope that it will be useful,
|
||||
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
+ * GNU General Public License for more details.
|
||||
+ *
|
||||
+ * Copyright (C) 2018 SiFive, Inc.
|
||||
+ */
|
||||
+
|
||||
+#include <linux/clkdev.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/slab.h>
|
||||
+
|
||||
+struct sifive_gemgxl_mgmt {
|
||||
+ void __iomem *reg;
|
||||
+ unsigned long rate;
|
||||
+ struct clk_hw hw;
|
||||
+};
|
||||
+
|
||||
+#define to_sifive_gemgxl_mgmt(mgmt) container_of(mgmt, struct sifive_gemgxl_mgmt, hw)
|
||||
+
|
||||
+static unsigned long sifive_gemgxl_mgmt_recalc_rate(struct clk_hw *hw,
|
||||
+ unsigned long parent_rate)
|
||||
+{
|
||||
+ struct sifive_gemgxl_mgmt *mgmt = to_sifive_gemgxl_mgmt(hw);
|
||||
+ return mgmt->rate;
|
||||
+}
|
||||
+
|
||||
+static long sifive_gemgxl_mgmt_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long *parent_rate)
|
||||
+{
|
||||
+ if (WARN_ON(rate < 2500000)) {
|
||||
+ return 2500000;
|
||||
+ } else if (rate == 2500000) {
|
||||
+ return 2500000;
|
||||
+ } else if (WARN_ON(rate < 13750000)) {
|
||||
+ return 2500000;
|
||||
+ } else if (WARN_ON(rate < 25000000)) {
|
||||
+ return 25000000;
|
||||
+ } else if (rate == 25000000) {
|
||||
+ return 25000000;
|
||||
+ } else if (WARN_ON(rate < 75000000)) {
|
||||
+ return 25000000;
|
||||
+ } else if (WARN_ON(rate < 125000000)) {
|
||||
+ return 125000000;
|
||||
+ } else if (rate == 125000000) {
|
||||
+ return 125000000;
|
||||
+ } else {
|
||||
+ WARN_ON(rate > 125000000);
|
||||
+ return 125000000;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static int sifive_gemgxl_mgmt_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long parent_rate)
|
||||
+{
|
||||
+ struct sifive_gemgxl_mgmt *mgmt = to_sifive_gemgxl_mgmt(hw);
|
||||
+ rate = sifive_gemgxl_mgmt_round_rate(hw, rate, &parent_rate);
|
||||
+ iowrite32(rate != 125000000, mgmt->reg);
|
||||
+ mgmt->rate = rate;
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct clk_ops sifive_gemgxl_mgmt_ops = {
|
||||
+ .recalc_rate = sifive_gemgxl_mgmt_recalc_rate,
|
||||
+ .round_rate = sifive_gemgxl_mgmt_round_rate,
|
||||
+ .set_rate = sifive_gemgxl_mgmt_set_rate,
|
||||
+};
|
||||
+
|
||||
+static int sifive_gemgxl_mgmt_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct clk_init_data init;
|
||||
+ struct sifive_gemgxl_mgmt *mgmt;
|
||||
+ struct resource *res;
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ mgmt = devm_kzalloc(&pdev->dev, sizeof(*mgmt), GFP_KERNEL);
|
||||
+ if (!mgmt)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ mgmt->reg = devm_ioremap_resource(&pdev->dev, res);
|
||||
+ if (IS_ERR(mgmt->reg))
|
||||
+ return PTR_ERR(mgmt->reg);
|
||||
+
|
||||
+ init.name = pdev->dev.of_node->name;
|
||||
+ init.ops = &sifive_gemgxl_mgmt_ops;
|
||||
+ init.flags = 0;
|
||||
+ init.num_parents = 0;
|
||||
+
|
||||
+ mgmt->rate = 0;
|
||||
+ mgmt->hw.init = &init;
|
||||
+
|
||||
+ clk = clk_register(NULL, &mgmt->hw);
|
||||
+ if (IS_ERR(clk))
|
||||
+ return PTR_ERR(clk);
|
||||
+
|
||||
+ of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, clk);
|
||||
+
|
||||
+ dev_info(&pdev->dev, "Registered clock switch '%s'\n", init.name);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id sifive_gemgxl_mgmt_of_match[] = {
|
||||
+ { .compatible = "sifive,cadencegemgxlmgmt0", },
|
||||
+ {}
|
||||
+};
|
||||
+
|
||||
+static struct platform_driver sifive_gemgxl_mgmt_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "sifive-gemgxl-mgmt",
|
||||
+ .of_match_table = sifive_gemgxl_mgmt_of_match,
|
||||
+ },
|
||||
+ .probe = sifive_gemgxl_mgmt_probe,
|
||||
+};
|
||||
+
|
||||
+static int __init sifive_gemgxl_mgmt_init(void)
|
||||
+{
|
||||
+ return platform_driver_register(&sifive_gemgxl_mgmt_driver);
|
||||
+}
|
||||
+core_initcall(sifive_gemgxl_mgmt_init);
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
From b7bd34686a9341a7d1cde3fc0b607706b3a82b40 Mon Sep 17 00:00:00 2001
|
||||
From: "Wesley W. Terpstra" <wesley@sifive.com>
|
||||
Date: Tue, 13 Feb 2018 19:39:41 -0800
|
||||
Subject: [PATCH 05/11] u54-prci: driver for core U54 clocks
|
||||
|
||||
---
|
||||
.../devicetree/bindings/clock/sifive,u54-prci.txt | 44 ++++++++++++++++++++++
|
||||
1 file changed, 44 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/clock/sifive,u54-prci.txt
|
||||
|
||||
diff --git a/Documentation/devicetree/bindings/clock/sifive,u54-prci.txt b/Documentation/devicetree/bindings/clock/sifive,u54-prci.txt
|
||||
new file mode 100644
|
||||
index 00000000..88682c5e
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/clock/sifive,u54-prci.txt
|
||||
@@ -0,0 +1,44 @@
|
||||
+SiFive U54 SoC clocks
|
||||
+
|
||||
+This binding uses the common clock binding:
|
||||
+ Documentation/devicetree/bindings/clock/clock-bindings.txt
|
||||
+
|
||||
+The U54 PRCI controller generates clocks for the U54 SoC. There is
|
||||
+a core PLL that sets the processor frequency and PLLs for ethernet
|
||||
+and DDR. It takes an input clock from the board, typically an oscillator
|
||||
+or crystal.
|
||||
+
|
||||
+Required properties:
|
||||
+- compatible: Should be "sifive,aloeprci0"
|
||||
+- #clock-cells: Should be <1>
|
||||
+- reg: Specifies base physical address and size of the registers
|
||||
+- clocks: phandles to the parent clock used as input
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+ refclk: refclk {
|
||||
+ #clock-cells = <0>;
|
||||
+ compatible = "fixed-clock";
|
||||
+ clock-frequency = <33333333>;
|
||||
+ clock-output-names = "xtal";
|
||||
+ };
|
||||
+
|
||||
+ u54: prci@10000000 {
|
||||
+ compatible = "sifive,aloeprci0";
|
||||
+ reg = <0x0 0x10000000 0x0 0x1000>;
|
||||
+ clocks = <&refclk>;
|
||||
+ #clock-cells = <1>;
|
||||
+ };
|
||||
+
|
||||
+ tlclk: tlclk {
|
||||
+ compatible = "fixed-factor-clock";
|
||||
+ clocks = <&u54 0>; /* Core frequency */
|
||||
+ #clock-cells = <0>;
|
||||
+ clock-div = <2>;
|
||||
+ clock-mult = <1>;
|
||||
+ };
|
||||
+
|
||||
+ ethernet@10090000 {
|
||||
+ ...
|
||||
+ clocks = <&prci 1>; /* TX clock */
|
||||
+ };
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
From ca0f9319165164a75216957bf93e33fd18548d21 Mon Sep 17 00:00:00 2001
|
||||
From: "Wesley W. Terpstra" <wesley@sifive.com>
|
||||
Date: Tue, 13 Feb 2018 19:39:41 -0800
|
||||
Subject: [PATCH 06/11] u54-prci: driver for core U54 clocks
|
||||
|
||||
---
|
||||
drivers/clk/sifive/u54-prci.c | 314 ++++++++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 314 insertions(+)
|
||||
create mode 100644 drivers/clk/sifive/u54-prci.c
|
||||
|
||||
diff --git a/drivers/clk/sifive/u54-prci.c b/drivers/clk/sifive/u54-prci.c
|
||||
new file mode 100644
|
||||
index 00000000..edc4b781
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/sifive/u54-prci.c
|
||||
@@ -0,0 +1,314 @@
|
||||
+/*
|
||||
+ * This program is free software; you can redistribute it and/or modify
|
||||
+ * it under the terms of the GNU General Public License version 2 as
|
||||
+ * published by the Free Software Foundation.
|
||||
+ *
|
||||
+ * This program is distributed in the hope that it will be useful,
|
||||
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
+ * GNU General Public License for more details.
|
||||
+ *
|
||||
+ * Copyright (C) 2018 SiFive, Inc.
|
||||
+ */
|
||||
+
|
||||
+#include <linux/clkdev.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <linux/log2.h>
|
||||
+
|
||||
+#define CORE_CLOCK 0
|
||||
+#define GEMTX_CLOCK 1
|
||||
+#define PRCI_CLOCKS 2
|
||||
+
|
||||
+#define MIN_REF 7000000UL
|
||||
+#define MAX_REF 200000000UL
|
||||
+#define MAX_PARENT 600000000UL
|
||||
+#define MAX_VCO 4800000000UL
|
||||
+#define MAX_DIV 64
|
||||
+#define MAX_R 64UL
|
||||
+
|
||||
+#define PLL_LOCK 0x80000000U
|
||||
+#define NAME_LEN 40
|
||||
+
|
||||
+struct sifive_u54_prci_driver;
|
||||
+
|
||||
+struct sifive_u54_prci_pll {
|
||||
+ struct clk_hw hw;
|
||||
+ struct sifive_u54_prci_driver *driver;
|
||||
+ char name[NAME_LEN];
|
||||
+ u32 freq;
|
||||
+ u32 glcm;
|
||||
+};
|
||||
+
|
||||
+struct sifive_u54_prci_driver {
|
||||
+ struct clk_onecell_data table;
|
||||
+ struct clk *clks[PRCI_CLOCKS];
|
||||
+ struct sifive_u54_prci_pll plls[PRCI_CLOCKS];
|
||||
+ void __iomem *reg;
|
||||
+};
|
||||
+
|
||||
+#define to_sifive_u54_prci_pll(hw) container_of(hw, struct sifive_u54_prci_pll, hw)
|
||||
+
|
||||
+struct sifive_u54_pll_cfg {
|
||||
+ unsigned long r, f, q, a;
|
||||
+};
|
||||
+
|
||||
+static struct sifive_u54_pll_cfg sifive_u54_pll_cfg(u32 reg)
|
||||
+{
|
||||
+ struct sifive_u54_pll_cfg cfg;
|
||||
+ cfg.r = (reg >> 0) & 0x3f;
|
||||
+ cfg.f = (reg >> 6) & 0x1ff;
|
||||
+ cfg.q = (reg >> 15) & 0x7;
|
||||
+ cfg.a = (reg >> 18) & 0x7;
|
||||
+ return cfg;
|
||||
+}
|
||||
+
|
||||
+static u32 sifive_u54_pll_reg(struct sifive_u54_pll_cfg cfg)
|
||||
+{
|
||||
+ u32 reg = 0;
|
||||
+ reg |= (cfg.r & 0x3f) << 0;
|
||||
+ reg |= (cfg.f & 0x1ff) << 6;
|
||||
+ reg |= (cfg.q & 0x7) << 15;
|
||||
+ reg |= (cfg.a & 0x7) << 18;
|
||||
+ reg |= 1<<25; // internal feedback
|
||||
+ return reg;
|
||||
+}
|
||||
+
|
||||
+static unsigned long sifive_u54_pll_rate(struct sifive_u54_pll_cfg cfg, unsigned long parent)
|
||||
+{
|
||||
+ return (parent*2*(cfg.f+1) / (cfg.r+1)) >> cfg.q;
|
||||
+}
|
||||
+
|
||||
+static struct sifive_u54_pll_cfg sifive_u54_pll_configure(unsigned long target, unsigned long parent)
|
||||
+{
|
||||
+ struct sifive_u54_pll_cfg cfg;
|
||||
+ unsigned long scale, ratio, best_delta, filter;
|
||||
+ u32 max_r, best_r, best_f, r;
|
||||
+
|
||||
+ /* Confirm input frequency is within bounds */
|
||||
+ if (WARN_ON(parent > MAX_PARENT)) { parent = MAX_PARENT; }
|
||||
+ if (WARN_ON(parent < MIN_REF)) { parent = MIN_REF; }
|
||||
+
|
||||
+ /* Calculate the Q shift and target VCO */
|
||||
+ scale = MAX_VCO / target;
|
||||
+ if (scale <= 1) {
|
||||
+ cfg.q = 1;
|
||||
+ target = MAX_VCO;
|
||||
+ } else if (scale > MAX_DIV) {
|
||||
+ cfg.q = ilog2(MAX_DIV);
|
||||
+ target = MAX_VCO/2;
|
||||
+ } else {
|
||||
+ cfg.q = ilog2(scale);
|
||||
+ target = target << cfg.q;
|
||||
+ }
|
||||
+
|
||||
+ /* Precalcualte the target ratio */
|
||||
+ ratio = (target << 20) / parent;
|
||||
+
|
||||
+ /* Placeholder values */
|
||||
+ best_r = 0;
|
||||
+ best_f = 0;
|
||||
+ best_delta = MAX_VCO;
|
||||
+
|
||||
+ /* Consider all values for R which land within [MIN_REF, MAX_REF]; prefer smaller R */
|
||||
+ max_r = min(MAX_R, parent / MIN_REF);
|
||||
+ for (r = DIV_ROUND_UP(parent, MAX_REF); r <= max_r; ++r) {
|
||||
+ /* What is the best F we can pick in this case? */
|
||||
+ u32 f = (ratio*r + (1<<20)) >> 21;
|
||||
+ unsigned long ref = parent / r;
|
||||
+ unsigned long vco = ref * f * 2;
|
||||
+ unsigned long delta;
|
||||
+
|
||||
+ /* Ensure rounding didn't take us out of range */
|
||||
+ if (vco > target) --f;
|
||||
+ if (vco < MAX_VCO/2) ++f;
|
||||
+ vco = ref * f * 2;
|
||||
+
|
||||
+ delta = abs(target - vco);
|
||||
+ if (delta < best_delta) {
|
||||
+ best_delta = delta;
|
||||
+ best_r = r;
|
||||
+ best_f = f;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ cfg.r = best_r - 1;
|
||||
+ cfg.f = best_f - 1;
|
||||
+
|
||||
+ /* Pick the best PLL jitter filter */
|
||||
+ filter = parent / best_r;
|
||||
+ BUG_ON(filter < 7000000);
|
||||
+ if (filter < 11000000) {
|
||||
+ cfg.a = 1;
|
||||
+ } else if (filter < 18000000) {
|
||||
+ cfg.a = 2;
|
||||
+ } else if (filter < 30000000) {
|
||||
+ cfg.a = 3;
|
||||
+ } else if (filter < 50000000) {
|
||||
+ cfg.a = 4;
|
||||
+ } else if (filter < 80000000) {
|
||||
+ cfg.a = 5;
|
||||
+ } else if (filter < 130000000) {
|
||||
+ cfg.a = 6;
|
||||
+ } else {
|
||||
+ BUG_ON (filter > 200000000);
|
||||
+ cfg.a = 7;
|
||||
+ }
|
||||
+
|
||||
+ return cfg;
|
||||
+}
|
||||
+
|
||||
+static unsigned long sifive_u54_prci_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
||||
+{
|
||||
+ struct sifive_u54_prci_pll *pll = to_sifive_u54_prci_pll(hw);
|
||||
+ struct sifive_u54_prci_driver *driver = pll->driver;
|
||||
+
|
||||
+ u32 reg = ioread32(driver->reg + pll->freq);
|
||||
+ struct sifive_u54_pll_cfg cfg = sifive_u54_pll_cfg(reg);
|
||||
+
|
||||
+ return sifive_u54_pll_rate(cfg, parent_rate);
|
||||
+}
|
||||
+
|
||||
+static long sifive_u54_prci_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate)
|
||||
+{
|
||||
+ struct sifive_u54_pll_cfg cfg = sifive_u54_pll_configure(rate, *parent_rate);
|
||||
+ return sifive_u54_pll_rate(cfg, *parent_rate);
|
||||
+}
|
||||
+
|
||||
+static int sifive_u54_prci_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
|
||||
+{
|
||||
+ struct sifive_u54_prci_pll *pll = to_sifive_u54_prci_pll(hw);
|
||||
+ struct sifive_u54_prci_driver *driver = pll->driver;
|
||||
+
|
||||
+ struct sifive_u54_pll_cfg cfg = sifive_u54_pll_configure(rate, parent_rate);
|
||||
+ u32 reg = sifive_u54_pll_reg(cfg);
|
||||
+
|
||||
+ /* Switch to reg clock and reconfigure PLL */
|
||||
+ iowrite32(1, driver->reg + pll->glcm);
|
||||
+ iowrite32(reg, driver->reg + pll->freq);
|
||||
+
|
||||
+ /* Wait for lock and switch back to PLL */
|
||||
+ while (!(ioread32(driver->reg + pll->freq) & PLL_LOCK));
|
||||
+ iowrite32(0, driver->reg + pll->glcm);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct clk_ops sifive_u54_prci_ops_rw = {
|
||||
+ .recalc_rate = sifive_u54_prci_recalc_rate,
|
||||
+ .round_rate = sifive_u54_prci_round_rate,
|
||||
+ .set_rate = sifive_u54_prci_set_rate,
|
||||
+};
|
||||
+
|
||||
+static const struct clk_ops sifive_u54_prci_ops_ro = {
|
||||
+ .recalc_rate = sifive_u54_prci_recalc_rate,
|
||||
+};
|
||||
+
|
||||
+static ssize_t sifive_u54_pll_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
+{
|
||||
+ struct sifive_u54_prci_driver *driver = dev_get_drvdata(dev);
|
||||
+ return sprintf(buf, "%ld", clk_get_rate(driver->clks[0]));
|
||||
+}
|
||||
+
|
||||
+static ssize_t sifive_u54_pll_rate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
+{
|
||||
+ struct sifive_u54_prci_driver *driver = dev_get_drvdata(dev);
|
||||
+ unsigned long rate;
|
||||
+ char *endp;
|
||||
+
|
||||
+ rate = simple_strtoul(buf, &endp, 0);
|
||||
+ if (*endp != 0 && *endp != '\n')
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ clk_set_rate(driver->clks[0], rate);
|
||||
+ return count;
|
||||
+}
|
||||
+
|
||||
+static DEVICE_ATTR(rate, 0644, sifive_u54_pll_show, sifive_u54_pll_rate_store);
|
||||
+
|
||||
+static int sifive_u54_prci_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct clk_init_data init;
|
||||
+ struct sifive_u54_prci_driver *driver;
|
||||
+ struct resource *res;
|
||||
+ const char *parent;
|
||||
+ int i;
|
||||
+
|
||||
+ parent = of_clk_get_parent_name(dev->of_node, 0);
|
||||
+ if (!parent) {
|
||||
+ dev_err(dev, "No OF parent clocks found\n");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ driver = devm_kzalloc(dev, sizeof(*driver), GFP_KERNEL);
|
||||
+ if (!driver) {
|
||||
+ dev_err(dev, "Out of memory\n");
|
||||
+ return -ENOMEM;
|
||||
+ }
|
||||
+
|
||||
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ driver->reg = devm_ioremap_resource(dev, res);
|
||||
+ if (IS_ERR(driver->reg))
|
||||
+ return PTR_ERR(driver->reg);
|
||||
+
|
||||
+ /* Link the data structure */
|
||||
+ driver->table.clk_num = PRCI_CLOCKS;
|
||||
+ driver->table.clks = &driver->clks[0];
|
||||
+ dev_set_drvdata(dev, driver);
|
||||
+
|
||||
+ /* Describe the clocks */
|
||||
+ snprintf(driver->plls[CORE_CLOCK].name, NAME_LEN, "%s.core", dev->of_node->name);
|
||||
+ driver->plls[CORE_CLOCK].freq = 0x4;
|
||||
+ driver->plls[CORE_CLOCK].glcm = 0x24;
|
||||
+ snprintf(driver->plls[GEMTX_CLOCK].name, NAME_LEN, "%s.gemtx", dev->of_node->name);
|
||||
+ driver->plls[GEMTX_CLOCK].freq = 0x1c;
|
||||
+ driver->plls[GEMTX_CLOCK].glcm = 0; /* None; cannot be set_rate */
|
||||
+
|
||||
+ /* Export the clocks */
|
||||
+ for (i = 0; i < PRCI_CLOCKS; ++i) {
|
||||
+ init.name = &driver->plls[i].name[0];
|
||||
+ init.ops = driver->plls[i].glcm ? &sifive_u54_prci_ops_rw : &sifive_u54_prci_ops_ro;
|
||||
+ init.num_parents = 1;
|
||||
+ init.parent_names = &parent;
|
||||
+ init.flags = 0;
|
||||
+
|
||||
+ driver->plls[i].driver = driver;
|
||||
+ driver->plls[i].hw.init = &init;
|
||||
+
|
||||
+ driver->clks[i] = devm_clk_register(dev, &driver->plls[i].hw);
|
||||
+ if (IS_ERR(driver->clks[i])) {
|
||||
+ dev_err(dev, "Failed to register clock %d, %ld\n", i, PTR_ERR(driver->clks[i]));
|
||||
+ return PTR_ERR(driver->clks[i]);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, &driver->table);
|
||||
+ device_create_file(dev, &dev_attr_rate);
|
||||
+ dev_info(dev, "Registered U54 core clocks\n");
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id sifive_u54_prci_of_match[] = {
|
||||
+ { .compatible = "sifive,aloeprci0", },
|
||||
+ {}
|
||||
+};
|
||||
+
|
||||
+static struct platform_driver sifive_u54_prci_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "sifive-u54-prci",
|
||||
+ .of_match_table = sifive_u54_prci_of_match,
|
||||
+ },
|
||||
+ .probe = sifive_u54_prci_probe,
|
||||
+};
|
||||
+
|
||||
+static int __init sifive_u54_prci_init(void)
|
||||
+{
|
||||
+ return platform_driver_register(&sifive_u54_prci_driver);
|
||||
+}
|
||||
+core_initcall(sifive_u54_prci_init);
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@@ -0,0 +1,409 @@
|
||||
From 28447771a2dddf9c083e1eefa5848d03e83496c7 Mon Sep 17 00:00:00 2001
|
||||
From: "Wesley W. Terpstra" <wesley@sifive.com>
|
||||
Date: Wed, 21 Feb 2018 15:43:02 -0800
|
||||
Subject: [PATCH 07/11] gpio-sifive: support GPIO on SiFive SoCs
|
||||
|
||||
---
|
||||
.../devicetree/bindings/gpio/gpio-sifive.txt | 28 ++
|
||||
drivers/gpio/Kconfig | 7 +
|
||||
drivers/gpio/Makefile | 1 +
|
||||
drivers/gpio/gpio-sifive.c | 322 +++++++++++++++++++++
|
||||
4 files changed, 358 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/gpio/gpio-sifive.txt
|
||||
create mode 100644 drivers/gpio/gpio-sifive.c
|
||||
|
||||
diff --git a/Documentation/devicetree/bindings/gpio/gpio-sifive.txt b/Documentation/devicetree/bindings/gpio/gpio-sifive.txt
|
||||
new file mode 100644
|
||||
index 00000000..bf41eed8
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/gpio/gpio-sifive.txt
|
||||
@@ -0,0 +1,28 @@
|
||||
+SiFive GPIO controller bindings
|
||||
+
|
||||
+Required properties:
|
||||
+- compatible:
|
||||
+ - "sifive,gpio0"
|
||||
+- reg: Physical base address and length of the controller's registers.
|
||||
+- #gpio-cells : Should be 2
|
||||
+ - The first cell is the gpio offset number.
|
||||
+ - The second cell indicates the polarity of the GPIO
|
||||
+- gpio-controller : Marks the device node as a GPIO controller.
|
||||
+- interrupt-controller: Mark the device node as an interrupt controller
|
||||
+- #interrupt-cells : Should be 2.
|
||||
+ - The first cell is the GPIO offset number within the GPIO controller.
|
||||
+ - The second cell is the edge/level to use for interrupt generation.
|
||||
+- interrupts: Specify the interrupts, one per GPIO
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+gpio: gpio@10060000 {
|
||||
+ compatible = "sifive,gpio0";
|
||||
+ interrupt-parent = <&plic>;
|
||||
+ interrupts = <7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22>;
|
||||
+ reg = <0x0 0x10060000 0x0 0x1000>;
|
||||
+ gpio-controller;
|
||||
+ #gpio-cells = <2>;
|
||||
+ interrupt-controller;
|
||||
+ #interrupt-cells = <2>;
|
||||
+};
|
||||
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
|
||||
index 4f52c3a8..7755f49e 100644
|
||||
--- a/drivers/gpio/Kconfig
|
||||
+++ b/drivers/gpio/Kconfig
|
||||
@@ -439,6 +439,13 @@ config GPIO_REG
|
||||
A 32-bit single register GPIO fixed in/out implementation. This
|
||||
can be used to represent any register as a set of GPIO signals.
|
||||
|
||||
+config GPIO_SIFIVE
|
||||
+ bool "SiFive GPIO support"
|
||||
+ depends on OF_GPIO
|
||||
+ select GPIOLIB_IRQCHIP
|
||||
+ help
|
||||
+ Say yes here to support the GPIO device on SiFive SoCs.
|
||||
+
|
||||
config GPIO_SPEAR_SPICS
|
||||
bool "ST SPEAr13xx SPI Chip Select as GPIO support"
|
||||
depends on PLAT_SPEAR
|
||||
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
|
||||
index c256aff6..244a3696 100644
|
||||
--- a/drivers/gpio/Makefile
|
||||
+++ b/drivers/gpio/Makefile
|
||||
@@ -111,6 +111,7 @@ obj-$(CONFIG_GPIO_REG) += gpio-reg.o
|
||||
obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o
|
||||
obj-$(CONFIG_GPIO_SCH) += gpio-sch.o
|
||||
obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o
|
||||
+obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o
|
||||
obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o
|
||||
obj-$(CONFIG_GPIO_SPEAR_SPICS) += gpio-spear-spics.o
|
||||
obj-$(CONFIG_GPIO_SPRD) += gpio-sprd.o
|
||||
diff --git a/drivers/gpio/gpio-sifive.c b/drivers/gpio/gpio-sifive.c
|
||||
new file mode 100644
|
||||
index 00000000..6482ebbc
|
||||
--- /dev/null
|
||||
+++ b/drivers/gpio/gpio-sifive.c
|
||||
@@ -0,0 +1,322 @@
|
||||
+/*
|
||||
+ * SiFive GPIO driver
|
||||
+ *
|
||||
+ * Copyright (C) 2018 SiFive, Inc.
|
||||
+ *
|
||||
+ * This program is free software; you can redistribute it and/or modify
|
||||
+ * it under the terms of the GNU General Public License version 2 as
|
||||
+ * published by the Free Software Foundation.
|
||||
+ */
|
||||
+#include <linux/bitops.h>
|
||||
+#include <linux/device.h>
|
||||
+#include <linux/errno.h>
|
||||
+#include <linux/of_irq.h>
|
||||
+#include <linux/gpio/driver.h>
|
||||
+#include <linux/irqchip/chained_irq.h>
|
||||
+#include <linux/init.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/pinctrl/consumer.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/pm.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <linux/spinlock.h>
|
||||
+
|
||||
+#define GPIO_INPUT_VAL 0x00
|
||||
+#define GPIO_INPUT_EN 0x04
|
||||
+#define GPIO_OUTPUT_EN 0x08
|
||||
+#define GPIO_OUTPUT_VAL 0x0C
|
||||
+#define GPIO_RISE_IE 0x18
|
||||
+#define GPIO_RISE_IP 0x1C
|
||||
+#define GPIO_FALL_IE 0x20
|
||||
+#define GPIO_FALL_IP 0x24
|
||||
+#define GPIO_HIGH_IE 0x28
|
||||
+#define GPIO_HIGH_IP 0x2C
|
||||
+#define GPIO_LOW_IE 0x30
|
||||
+#define GPIO_LOW_IP 0x34
|
||||
+#define GPIO_OUTPUT_XOR 0x40
|
||||
+
|
||||
+#define MAX_GPIO 32
|
||||
+
|
||||
+struct sifive_gpio {
|
||||
+ raw_spinlock_t lock;
|
||||
+ void __iomem *base;
|
||||
+ struct gpio_chip gc;
|
||||
+ unsigned long enabled;
|
||||
+ unsigned trigger[MAX_GPIO];
|
||||
+ unsigned int irq_parent[MAX_GPIO];
|
||||
+ struct sifive_gpio *self_ptr[MAX_GPIO];
|
||||
+};
|
||||
+
|
||||
+static void sifive_assign_bit(void __iomem *ptr, int offset, int value)
|
||||
+{
|
||||
+ // It's frustrating that we are not allowed to use the device atomics
|
||||
+ // which are GUARANTEED to be supported by this device on RISC-V
|
||||
+ u32 bit = BIT(offset), old = ioread32(ptr);
|
||||
+ if (value)
|
||||
+ iowrite32(old | bit, ptr);
|
||||
+ else
|
||||
+ iowrite32(old & ~bit, ptr);
|
||||
+}
|
||||
+
|
||||
+static int sifive_direction_input(struct gpio_chip *gc, unsigned offset)
|
||||
+{
|
||||
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ if (offset >= gc->ngpio)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ raw_spin_lock_irqsave(&chip->lock, flags);
|
||||
+ sifive_assign_bit(chip->base + GPIO_OUTPUT_EN, offset, 0);
|
||||
+ sifive_assign_bit(chip->base + GPIO_INPUT_EN, offset, 1);
|
||||
+ raw_spin_unlock_irqrestore(&chip->lock, flags);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int sifive_direction_output(struct gpio_chip *gc, unsigned offset, int value)
|
||||
+{
|
||||
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ if (offset >= gc->ngpio)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ raw_spin_lock_irqsave(&chip->lock, flags);
|
||||
+ sifive_assign_bit(chip->base + GPIO_INPUT_EN, offset, 0);
|
||||
+ sifive_assign_bit(chip->base + GPIO_OUTPUT_VAL, offset, value);
|
||||
+ sifive_assign_bit(chip->base + GPIO_OUTPUT_EN, offset, 1);
|
||||
+ raw_spin_unlock_irqrestore(&chip->lock, flags);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int sifive_get_direction(struct gpio_chip *gc, unsigned offset)
|
||||
+{
|
||||
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
|
||||
+
|
||||
+ if (offset >= gc->ngpio)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ return !(ioread32(chip->base + GPIO_OUTPUT_EN) & BIT(offset));
|
||||
+}
|
||||
+
|
||||
+static int sifive_get_value(struct gpio_chip *gc, unsigned offset)
|
||||
+{
|
||||
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
|
||||
+
|
||||
+ if (offset >= gc->ngpio)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ return !!(ioread32(chip->base + GPIO_INPUT_VAL) & BIT(offset));
|
||||
+}
|
||||
+
|
||||
+static void sifive_set_value(struct gpio_chip *gc, unsigned offset, int value)
|
||||
+{
|
||||
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ if (offset >= gc->ngpio)
|
||||
+ return;
|
||||
+
|
||||
+ raw_spin_lock_irqsave(&chip->lock, flags);
|
||||
+ sifive_assign_bit(chip->base + GPIO_OUTPUT_VAL, offset, value);
|
||||
+ raw_spin_unlock_irqrestore(&chip->lock, flags);
|
||||
+}
|
||||
+
|
||||
+static void sifive_set_ie(struct sifive_gpio *chip, int offset)
|
||||
+{
|
||||
+ unsigned long flags;
|
||||
+ unsigned trigger;
|
||||
+
|
||||
+ raw_spin_lock_irqsave(&chip->lock, flags);
|
||||
+ trigger = (chip->enabled & BIT(offset)) ? chip->trigger[offset] : 0;
|
||||
+ sifive_assign_bit(chip->base + GPIO_RISE_IE, offset, trigger & IRQ_TYPE_EDGE_RISING);
|
||||
+ sifive_assign_bit(chip->base + GPIO_FALL_IE, offset, trigger & IRQ_TYPE_EDGE_FALLING);
|
||||
+ sifive_assign_bit(chip->base + GPIO_HIGH_IE, offset, trigger & IRQ_TYPE_LEVEL_HIGH);
|
||||
+ sifive_assign_bit(chip->base + GPIO_LOW_IE, offset, trigger & IRQ_TYPE_LEVEL_LOW);
|
||||
+ raw_spin_unlock_irqrestore(&chip->lock, flags);
|
||||
+}
|
||||
+
|
||||
+static int sifive_irq_set_type(struct irq_data *d, unsigned trigger)
|
||||
+{
|
||||
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
|
||||
+ int offset = irqd_to_hwirq(d);
|
||||
+
|
||||
+ if (offset < 0 || offset >= gc->ngpio)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ chip->trigger[offset] = trigger;
|
||||
+ sifive_set_ie(chip, offset);
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/* chained_irq_{enter,exit} already mask the parent */
|
||||
+static void sifive_irq_mask(struct irq_data *d) { }
|
||||
+static void sifive_irq_unmask(struct irq_data *d) { }
|
||||
+
|
||||
+static void sifive_irq_enable(struct irq_data *d)
|
||||
+{
|
||||
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
|
||||
+ int offset = irqd_to_hwirq(d) % MAX_GPIO; // must not fail
|
||||
+ u32 bit = BIT(offset);
|
||||
+
|
||||
+ /* Switch to input */
|
||||
+ sifive_direction_input(gc, offset);
|
||||
+
|
||||
+ /* Clear any sticky pending interrupts */
|
||||
+ iowrite32(bit, chip->base + GPIO_RISE_IP);
|
||||
+ iowrite32(bit, chip->base + GPIO_FALL_IP);
|
||||
+ iowrite32(bit, chip->base + GPIO_HIGH_IP);
|
||||
+ iowrite32(bit, chip->base + GPIO_LOW_IP);
|
||||
+
|
||||
+ /* Enable interrupts */
|
||||
+ assign_bit(offset, &chip->enabled, 1);
|
||||
+ sifive_set_ie(chip, offset);
|
||||
+}
|
||||
+
|
||||
+static void sifive_irq_disable(struct irq_data *d)
|
||||
+{
|
||||
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
|
||||
+ int offset = irqd_to_hwirq(d) % MAX_GPIO; // must not fail
|
||||
+
|
||||
+ assign_bit(offset, &chip->enabled, 0);
|
||||
+ sifive_set_ie(chip, offset);
|
||||
+}
|
||||
+
|
||||
+static struct irq_chip sifive_irqchip = {
|
||||
+ .name = "sifive-gpio",
|
||||
+ .irq_set_type = sifive_irq_set_type,
|
||||
+ .irq_mask = sifive_irq_mask,
|
||||
+ .irq_unmask = sifive_irq_unmask,
|
||||
+ .irq_enable = sifive_irq_enable,
|
||||
+ .irq_disable = sifive_irq_disable,
|
||||
+};
|
||||
+
|
||||
+static void sifive_irq_handler(struct irq_desc *desc)
|
||||
+{
|
||||
+ struct irq_chip *irqchip = irq_desc_get_chip(desc);
|
||||
+ struct sifive_gpio **self_ptr = irq_desc_get_handler_data(desc);
|
||||
+ struct sifive_gpio *chip = *self_ptr;
|
||||
+ int offset = self_ptr - &chip->self_ptr[0];
|
||||
+ u32 bit = BIT(offset);
|
||||
+
|
||||
+ chained_irq_enter(irqchip, desc);
|
||||
+
|
||||
+ /* Re-arm the edge triggers so don't miss the next one */
|
||||
+ iowrite32(bit, chip->base + GPIO_RISE_IP);
|
||||
+ iowrite32(bit, chip->base + GPIO_FALL_IP);
|
||||
+
|
||||
+ generic_handle_irq(irq_find_mapping(chip->gc.irq.domain, offset));
|
||||
+
|
||||
+ /* Re-arm the level triggers after handling to prevent spurious refire */
|
||||
+ iowrite32(bit, chip->base + GPIO_HIGH_IP);
|
||||
+ iowrite32(bit, chip->base + GPIO_LOW_IP);
|
||||
+
|
||||
+ chained_irq_exit(irqchip, desc);
|
||||
+}
|
||||
+
|
||||
+static int sifive_gpio_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct device_node *node = pdev->dev.of_node;
|
||||
+ struct sifive_gpio *chip;
|
||||
+ struct resource *res;
|
||||
+ int gpio, irq, ret, ngpio;
|
||||
+
|
||||
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
|
||||
+ if (!chip) {
|
||||
+ dev_err(dev, "out of memory\n");
|
||||
+ return -ENOMEM;
|
||||
+ }
|
||||
+
|
||||
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ chip->base = devm_ioremap_resource(dev, res);
|
||||
+ if (IS_ERR(chip->base)) {
|
||||
+ dev_err(dev, "failed to allocate device memory\n");
|
||||
+ return PTR_ERR(chip->base);
|
||||
+ }
|
||||
+
|
||||
+ ngpio = of_irq_count(node);
|
||||
+ if (ngpio >= MAX_GPIO) {
|
||||
+ dev_err(dev, "too many interrupts\n");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ raw_spin_lock_init(&chip->lock);
|
||||
+ chip->gc.direction_input = sifive_direction_input;
|
||||
+ chip->gc.direction_output = sifive_direction_output;
|
||||
+ chip->gc.get_direction = sifive_get_direction;
|
||||
+ chip->gc.get = sifive_get_value;
|
||||
+ chip->gc.set = sifive_set_value;
|
||||
+ chip->gc.base = -1;
|
||||
+ chip->gc.ngpio = ngpio;
|
||||
+ chip->gc.label = dev_name(dev);
|
||||
+ chip->gc.parent = dev;
|
||||
+ chip->gc.owner = THIS_MODULE;
|
||||
+
|
||||
+ ret = gpiochip_add_data(&chip->gc, chip);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ /* Disable all GPIO interrupts before enabling parent interrupts */
|
||||
+ iowrite32(0, chip->base + GPIO_RISE_IE);
|
||||
+ iowrite32(0, chip->base + GPIO_FALL_IE);
|
||||
+ iowrite32(0, chip->base + GPIO_HIGH_IE);
|
||||
+ iowrite32(0, chip->base + GPIO_LOW_IE);
|
||||
+ chip->enabled = 0;
|
||||
+
|
||||
+ ret = gpiochip_irqchip_add(&chip->gc, &sifive_irqchip, 0, handle_simple_irq, IRQ_TYPE_NONE);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "could not add irqchip\n");
|
||||
+ gpiochip_remove(&chip->gc);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ chip->gc.irq.num_parents = ngpio;
|
||||
+ chip->gc.irq.parents = &chip->irq_parent[0];
|
||||
+ chip->gc.irq.map = &chip->irq_parent[0];
|
||||
+
|
||||
+ for (gpio = 0; gpio < ngpio; ++gpio) {
|
||||
+ irq = platform_get_irq(pdev, gpio);
|
||||
+ if (irq < 0) {
|
||||
+ dev_err(dev, "invalid IRQ\n");
|
||||
+ gpiochip_remove(&chip->gc);
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+
|
||||
+ chip->irq_parent[gpio] = irq;
|
||||
+ chip->self_ptr[gpio] = chip;
|
||||
+ chip->trigger[gpio] = IRQ_TYPE_LEVEL_HIGH;
|
||||
+ }
|
||||
+
|
||||
+ for (gpio = 0; gpio < ngpio; ++gpio) {
|
||||
+ irq = chip->irq_parent[gpio];
|
||||
+ irq_set_chained_handler_and_data(irq, sifive_irq_handler, &chip->self_ptr[gpio]);
|
||||
+ irq_set_parent(irq_find_mapping(chip->gc.irq.domain, gpio), irq);
|
||||
+ }
|
||||
+
|
||||
+ platform_set_drvdata(pdev, chip);
|
||||
+ dev_info(dev, "SiFive GPIO chip registered %d GPIOs\n", ngpio);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id sifive_gpio_match[] = {
|
||||
+ {
|
||||
+ .compatible = "sifive,gpio0",
|
||||
+ },
|
||||
+ { },
|
||||
+};
|
||||
+
|
||||
+static struct platform_driver sifive_gpio_driver = {
|
||||
+ .probe = sifive_gpio_probe,
|
||||
+ .driver = {
|
||||
+ .name = "sifive_gpio",
|
||||
+ .of_match_table = of_match_ptr(sifive_gpio_match),
|
||||
+ },
|
||||
+};
|
||||
+builtin_platform_driver(sifive_gpio_driver)
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
From e6655fae27290b94c082069da779851dcce40f00 Mon Sep 17 00:00:00 2001
|
||||
From: "Wesley W. Terpstra" <wesley@sifive.com>
|
||||
Date: Fri, 2 Mar 2018 14:53:51 -0800
|
||||
Subject: [PATCH 08/11] pwm-sifive: add a driver for SiFive SoC PWM
|
||||
|
||||
---
|
||||
.../devicetree/bindings/pwm/pwm-sifive.txt | 28 +++
|
||||
drivers/pwm/Kconfig | 10 +
|
||||
drivers/pwm/Makefile | 1 +
|
||||
drivers/pwm/pwm-sifive.c | 252 +++++++++++++++++++++
|
||||
4 files changed, 291 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/pwm/pwm-sifive.txt
|
||||
create mode 100644 drivers/pwm/pwm-sifive.c
|
||||
|
||||
diff --git a/Documentation/devicetree/bindings/pwm/pwm-sifive.txt b/Documentation/devicetree/bindings/pwm/pwm-sifive.txt
|
||||
new file mode 100644
|
||||
index 00000000..7cea20db
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/pwm/pwm-sifive.txt
|
||||
@@ -0,0 +1,28 @@
|
||||
+SiFive PWM controller
|
||||
+
|
||||
+Unlike most other PWM controllers, the SiFive PWM controller currently only
|
||||
+supports one period for all channels in the PWM. This is set globally in DTS.
|
||||
+The period also has significant restrictions on the values it can achieve,
|
||||
+which the driver rounds to the nearest achievable frequency.
|
||||
+
|
||||
+Required properties:
|
||||
+- compatible: should be "sifive,pwm0"
|
||||
+- reg: physical base address and length of the controller's registers
|
||||
+- clocks: The frequency the controller runs at
|
||||
+- #pwm-cells: Should be 2.
|
||||
+ The first cell is the PWM channel number
|
||||
+ The second cell is the PWM polarity
|
||||
+- sifive,approx-period: the driver will get as close to this period as it can
|
||||
+- interrupts: one interrupt per PWM channel (currently unused in the driver)
|
||||
+
|
||||
+Examples:
|
||||
+
|
||||
+pwm: pwm@10020000 {
|
||||
+ compatible = "sifive,pwm0";
|
||||
+ reg = <0x0 0x10020000 0x0 0x1000>;
|
||||
+ clocks = <&tlclk>;
|
||||
+ interrupt-parent = <&plic>;
|
||||
+ interrupts = <42 43 44 45>;
|
||||
+ #pwm-cells = <2>;
|
||||
+ sifive,approx-period = <1000000>;
|
||||
+};
|
||||
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
|
||||
index 504d2527..dd12144d 100644
|
||||
--- a/drivers/pwm/Kconfig
|
||||
+++ b/drivers/pwm/Kconfig
|
||||
@@ -378,6 +378,16 @@ config PWM_SAMSUNG
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called pwm-samsung.
|
||||
|
||||
+config PWM_SIFIVE
|
||||
+ tristate "SiFive PWM support"
|
||||
+ depends on OF
|
||||
+ depends on COMMON_CLK
|
||||
+ help
|
||||
+ Generic PWM framework driver for SiFive SoCs.
|
||||
+
|
||||
+ To compile this driver as a module, choose M here: the module
|
||||
+ will be called pwm-sifive.
|
||||
+
|
||||
config PWM_SPEAR
|
||||
tristate "STMicroelectronics SPEAr PWM support"
|
||||
depends on PLAT_SPEAR
|
||||
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
|
||||
index 9c676a0d..30089ca6 100644
|
||||
--- a/drivers/pwm/Makefile
|
||||
+++ b/drivers/pwm/Makefile
|
||||
@@ -37,6 +37,7 @@ obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
|
||||
obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
|
||||
obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
|
||||
obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
|
||||
+obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
|
||||
obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o
|
||||
obj-$(CONFIG_PWM_STI) += pwm-sti.o
|
||||
obj-$(CONFIG_PWM_STM32) += pwm-stm32.o
|
||||
diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c
|
||||
new file mode 100644
|
||||
index 00000000..93bc0844
|
||||
--- /dev/null
|
||||
+++ b/drivers/pwm/pwm-sifive.c
|
||||
@@ -0,0 +1,252 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2018 SiFive, Inc
|
||||
+ *
|
||||
+ * This program is free software; you can redistribute it and/or modify it
|
||||
+ * under the terms of the GNU General Public License version 2, as published by
|
||||
+ * the Free Software Foundation.
|
||||
+ */
|
||||
+
|
||||
+#include <dt-bindings/pwm/pwm.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/pwm.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/io.h>
|
||||
+
|
||||
+#define MAX_PWM 4
|
||||
+
|
||||
+/* Register offsets */
|
||||
+#define REG_PWMCFG 0x0
|
||||
+#define REG_PWMCOUNT 0x8
|
||||
+#define REG_PWMS 0x10
|
||||
+#define REG_PWMCMP0 0x20
|
||||
+
|
||||
+/* PWMCFG fields */
|
||||
+#define BIT_PWM_SCALE 0
|
||||
+#define BIT_PWM_STICKY 8
|
||||
+#define BIT_PWM_ZERO_ZMP 9
|
||||
+#define BIT_PWM_DEGLITCH 10
|
||||
+#define BIT_PWM_EN_ALWAYS 12
|
||||
+#define BIT_PWM_EN_ONCE 13
|
||||
+#define BIT_PWM0_CENTER 16
|
||||
+#define BIT_PWM0_GANG 24
|
||||
+#define BIT_PWM0_IP 28
|
||||
+
|
||||
+#define SIZE_PWMCMP 4
|
||||
+#define MASK_PWM_SCALE 0xf
|
||||
+
|
||||
+struct sifive_pwm_device {
|
||||
+ struct pwm_chip chip;
|
||||
+ struct notifier_block notifier;
|
||||
+ struct clk *clk;
|
||||
+ void __iomem *regs;
|
||||
+ int irq;
|
||||
+ unsigned int approx_period;
|
||||
+ unsigned int real_period;
|
||||
+};
|
||||
+
|
||||
+static inline struct sifive_pwm_device *chip_to_sifive(struct pwm_chip *c)
|
||||
+{
|
||||
+ return container_of(c, struct sifive_pwm_device, chip);
|
||||
+}
|
||||
+
|
||||
+static inline struct sifive_pwm_device *notifier_to_sifive(struct notifier_block *nb)
|
||||
+{
|
||||
+ return container_of(nb, struct sifive_pwm_device, notifier);
|
||||
+}
|
||||
+
|
||||
+static int sifive_pwm_apply(struct pwm_chip *chip, struct pwm_device *dev, struct pwm_state *state)
|
||||
+{
|
||||
+ struct sifive_pwm_device *pwm = chip_to_sifive(chip);
|
||||
+ unsigned int duty_cycle;
|
||||
+ u32 frac;
|
||||
+
|
||||
+ duty_cycle = state->duty_cycle;
|
||||
+ if (!state->enabled) duty_cycle = 0;
|
||||
+ if (state->polarity == PWM_POLARITY_NORMAL) duty_cycle = state->period - duty_cycle;
|
||||
+
|
||||
+ frac = ((u64)duty_cycle << 16) / state->period;
|
||||
+ frac = min(frac, 0xFFFFU);
|
||||
+
|
||||
+ iowrite32(frac, pwm->regs + REG_PWMCMP0 + (dev->hwpwm * SIZE_PWMCMP));
|
||||
+
|
||||
+ if (state->enabled) {
|
||||
+ state->period = pwm->real_period;
|
||||
+ state->duty_cycle = ((u64)frac * pwm->real_period) >> 16;
|
||||
+ if (state->polarity == PWM_POLARITY_NORMAL)
|
||||
+ state->duty_cycle = state->period - state->duty_cycle;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void sifive_pwm_get_state(struct pwm_chip *chip, struct pwm_device *dev, struct pwm_state *state)
|
||||
+{
|
||||
+ struct sifive_pwm_device *pwm = chip_to_sifive(chip);
|
||||
+ unsigned long duty;
|
||||
+
|
||||
+ duty = ioread32(pwm->regs + REG_PWMCMP0 + (dev->hwpwm * SIZE_PWMCMP));
|
||||
+
|
||||
+ state->period = pwm->real_period;
|
||||
+ state->duty_cycle = ((u64)duty * pwm->real_period) >> 16;
|
||||
+ state->polarity = PWM_POLARITY_INVERSED;
|
||||
+ state->enabled = duty > 0;
|
||||
+}
|
||||
+
|
||||
+static const struct pwm_ops sifive_pwm_ops = {
|
||||
+ .get_state = sifive_pwm_get_state,
|
||||
+ .apply = sifive_pwm_apply,
|
||||
+ .owner = THIS_MODULE,
|
||||
+};
|
||||
+
|
||||
+static struct pwm_device *sifive_pwm_xlate(struct pwm_chip *chip, const struct of_phandle_args *args)
|
||||
+{
|
||||
+ struct sifive_pwm_device *pwm = chip_to_sifive(chip);
|
||||
+ struct pwm_device *dev;
|
||||
+
|
||||
+ if (args->args[0] >= chip->npwm)
|
||||
+ return ERR_PTR(-EINVAL);
|
||||
+
|
||||
+ dev = pwm_request_from_chip(chip, args->args[0], NULL);
|
||||
+ if (IS_ERR(dev))
|
||||
+ return dev;
|
||||
+
|
||||
+ /* The period cannot be changed on a per-PWM basis */
|
||||
+ dev->args.period = pwm->real_period;
|
||||
+ dev->args.polarity = PWM_POLARITY_NORMAL;
|
||||
+ if (args->args[1] & PWM_POLARITY_INVERTED)
|
||||
+ dev->args.polarity = PWM_POLARITY_INVERSED;
|
||||
+
|
||||
+ return dev;
|
||||
+}
|
||||
+
|
||||
+static void sifive_pwm_update_clock(struct sifive_pwm_device *pwm, unsigned long rate)
|
||||
+{
|
||||
+ /* (1 << (16+scale)) * 10^9/rate = real_period */
|
||||
+ unsigned long scalePow = (pwm->approx_period * (u64)rate) / 1000000000;
|
||||
+ int scale = ilog2(scalePow) - 16;
|
||||
+
|
||||
+ scale = clamp(scale, 0, 0xf);
|
||||
+ iowrite32((1 << BIT_PWM_EN_ALWAYS) | (scale << BIT_PWM_SCALE), pwm->regs + REG_PWMCFG);
|
||||
+
|
||||
+ pwm->real_period = (1000000000ULL << (16 + scale)) / rate;
|
||||
+}
|
||||
+
|
||||
+static int sifive_pwm_clock_notifier(struct notifier_block *nb, unsigned long event, void *data)
|
||||
+{
|
||||
+ struct clk_notifier_data *ndata = data;
|
||||
+ struct sifive_pwm_device *pwm = notifier_to_sifive(nb);
|
||||
+
|
||||
+ if (event == POST_RATE_CHANGE)
|
||||
+ sifive_pwm_update_clock(pwm, ndata->new_rate);
|
||||
+
|
||||
+ return NOTIFY_OK;
|
||||
+}
|
||||
+
|
||||
+static int sifive_pwm_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct device_node *node = pdev->dev.of_node;
|
||||
+ struct sifive_pwm_device *pwm;
|
||||
+ struct pwm_chip *chip;
|
||||
+ struct resource *res;
|
||||
+ int ret;
|
||||
+
|
||||
+ pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
|
||||
+ if (!pwm) {
|
||||
+ dev_err(dev, "Out of memory\n");
|
||||
+ return -ENOMEM;
|
||||
+ }
|
||||
+
|
||||
+ chip = &pwm->chip;
|
||||
+ chip->dev = dev;
|
||||
+ chip->ops = &sifive_pwm_ops;
|
||||
+ chip->of_xlate = sifive_pwm_xlate;
|
||||
+ chip->of_pwm_n_cells = 2;
|
||||
+ chip->base = -1;
|
||||
+
|
||||
+ ret = of_property_read_u32(node, "sifive,npwm", &chip->npwm);
|
||||
+ if (ret < 0 || chip->npwm > MAX_PWM) chip->npwm = MAX_PWM;
|
||||
+
|
||||
+ ret = of_property_read_u32(node, "sifive,approx-period", &pwm->approx_period);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "Unable to read sifive,approx-period from DTS\n");
|
||||
+ return -ENOENT;
|
||||
+ }
|
||||
+
|
||||
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ pwm->regs = devm_ioremap_resource(dev, res);
|
||||
+ if (IS_ERR(pwm->regs)) {
|
||||
+ dev_err(dev, "Unable to map IO resources\n");
|
||||
+ return PTR_ERR(pwm->regs);
|
||||
+ }
|
||||
+
|
||||
+ pwm->clk = devm_clk_get(dev, NULL);
|
||||
+ if (IS_ERR(pwm->clk)) {
|
||||
+ dev_err(dev, "Unable to find controller clock\n");
|
||||
+ return PTR_ERR(pwm->clk);
|
||||
+ }
|
||||
+
|
||||
+ pwm->irq = platform_get_irq(pdev, 0);
|
||||
+ if (pwm->irq < 0) {
|
||||
+ dev_err(dev, "Unable to find interrupt\n");
|
||||
+ return pwm->irq;
|
||||
+ }
|
||||
+
|
||||
+ /* Watch for changes to underlying clock frequency */
|
||||
+ pwm->notifier.notifier_call = sifive_pwm_clock_notifier;
|
||||
+ clk_notifier_register(pwm->clk, &pwm->notifier);
|
||||
+
|
||||
+ /* Initialize PWM config */
|
||||
+ sifive_pwm_update_clock(pwm, clk_get_rate(pwm->clk));
|
||||
+
|
||||
+ /* No interrupt handler needed yet */
|
||||
+/*
|
||||
+ ret = devm_request_irq(dev, pwm->irq, sifive_pwm_irq, 0, dev_name(dev), pwm);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "Unable to bind interrupt\n");
|
||||
+ return ret;
|
||||
+ }
|
||||
+*/
|
||||
+
|
||||
+ ret = pwmchip_add(chip);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "cannot register PWM: %d\n", ret);
|
||||
+ clk_notifier_unregister(pwm->clk, &pwm->notifier);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ platform_set_drvdata(pdev, pwm);
|
||||
+ dev_info(dev, "SiFive PWM chip registered %d PWMs\n", chip->npwm);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int sifive_pwm_remove(struct platform_device *dev)
|
||||
+{
|
||||
+ struct sifive_pwm_device *pwm = platform_get_drvdata(dev);
|
||||
+ struct pwm_chip *chip = &pwm->chip;
|
||||
+
|
||||
+ clk_notifier_unregister(pwm->clk, &pwm->notifier);
|
||||
+ return pwmchip_remove(chip);
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id sifive_pwm_of_match[] = {
|
||||
+ { .compatible = "sifive,pwm0" },
|
||||
+ {},
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, sifive_pwm_of_match);
|
||||
+
|
||||
+static struct platform_driver sifive_pwm_driver = {
|
||||
+ .probe = sifive_pwm_probe,
|
||||
+ .remove = sifive_pwm_remove,
|
||||
+ .driver = {
|
||||
+ .name = "pwm-sifivem",
|
||||
+ .of_match_table = of_match_ptr(sifive_pwm_of_match),
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(sifive_pwm_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("SiFive PWM driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
From aa230e7dc2ab01db5b630f427e57297ffc25c884 Mon Sep 17 00:00:00 2001
|
||||
From: Atish Patra <atish.patra@wdc.com>
|
||||
Date: Fri, 7 Sep 2018 10:22:27 -0700
|
||||
Subject: [PATCH 09/11] RISC-V: Networking fix Hack
|
||||
|
||||
It looks like that kernel driver now supports reseting the
|
||||
signal one additional time. As it had been already reset
|
||||
twice in FSBL, PHY gets into incorrect state causing below error.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
macb 10090000.ethernet (unnamed net_device) (uninitialized): Could not attach to PHY
|
||||
macb: probe of 10090000.ethernet failed with error -110
|
||||
----------------------------------------------------------------------
|
||||
|
||||
This patch is just a temporary fix until we have a fix a FSBL.
|
||||
It is just a **HACK** and **NOT TO BE MERGED** into mainline.
|
||||
|
||||
Signed-off-by: Atish Patra <atish.patra@wdc.com>
|
||||
---
|
||||
drivers/net/phy/mdio_bus.c | 3 ---
|
||||
1 file changed, 3 deletions(-)
|
||||
|
||||
diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
|
||||
index 98f4b1f7..02c31f83 100644
|
||||
--- a/drivers/net/phy/mdio_bus.c
|
||||
+++ b/drivers/net/phy/mdio_bus.c
|
||||
@@ -64,9 +64,6 @@ static int mdiobus_register_gpiod(struct mdio_device *mdiodev)
|
||||
|
||||
mdiodev->reset = gpiod;
|
||||
|
||||
- /* Assert the reset signal again */
|
||||
- mdio_device_reset(mdiodev, 1);
|
||||
-
|
||||
return 0;
|
||||
}
|
||||
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@@ -0,0 +1,804 @@
|
||||
From 1fd6c8fbc07bec3c18771738ce4b2aad3ed228f2 Mon Sep 17 00:00:00 2001
|
||||
From: "Wesley W. Terpstra" <wesley@sifive.com>
|
||||
Date: Wed, 25 Apr 2018 12:10:15 -0700
|
||||
Subject: [PATCH 10/11] pcie-microsemi: added support for the Vera-board root
|
||||
complex
|
||||
|
||||
---
|
||||
drivers/pci/controller/Kconfig | 6 +
|
||||
drivers/pci/controller/Makefile | 1 +
|
||||
drivers/pci/controller/pcie-microsemi.c | 754 ++++++++++++++++++++++++++++++++
|
||||
3 files changed, 761 insertions(+)
|
||||
create mode 100644 drivers/pci/controller/pcie-microsemi.c
|
||||
|
||||
diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
|
||||
index 028b2874..4458c119 100644
|
||||
--- a/drivers/pci/controller/Kconfig
|
||||
+++ b/drivers/pci/controller/Kconfig
|
||||
@@ -278,5 +278,11 @@ config VMD
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called vmd.
|
||||
|
||||
+config PCIE_MICROSEMI
|
||||
+ bool "Microsemi AXI PCIe host bridge support"
|
||||
+ depends on OF || COMPILE_TEST
|
||||
+ help
|
||||
+ Say 'Y' here if you want kernel to support the Microsemi AXI PCIe
|
||||
+ Host Bridge driver.
|
||||
source "drivers/pci/controller/dwc/Kconfig"
|
||||
endmenu
|
||||
diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile
|
||||
index d56a5074..c3b76ff2 100644
|
||||
--- a/drivers/pci/controller/Makefile
|
||||
+++ b/drivers/pci/controller/Makefile
|
||||
@@ -28,6 +28,7 @@ obj-$(CONFIG_PCIE_ROCKCHIP_HOST) += pcie-rockchip-host.o
|
||||
obj-$(CONFIG_PCIE_MEDIATEK) += pcie-mediatek.o
|
||||
obj-$(CONFIG_PCIE_MOBIVEIL) += pcie-mobiveil.o
|
||||
obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o
|
||||
+obj-$(CONFIG_PCIE_MICROSEMI) += pcie-microsemi.o
|
||||
obj-$(CONFIG_VMD) += vmd.o
|
||||
# pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW
|
||||
obj-y += dwc/
|
||||
diff --git a/drivers/pci/controller/pcie-microsemi.c b/drivers/pci/controller/pcie-microsemi.c
|
||||
new file mode 100644
|
||||
index 00000000..9e2abca2
|
||||
--- /dev/null
|
||||
+++ b/drivers/pci/controller/pcie-microsemi.c
|
||||
@@ -0,0 +1,754 @@
|
||||
+/*
|
||||
+ * PCIe host controller driver for Microsemi AXI PCIe Bridge
|
||||
+ *
|
||||
+ * Copyright (c) 2018 - Microsemi.
|
||||
+ * Author: Badal Nilawar <badal.nilawar@microsemi.com>
|
||||
+ *
|
||||
+ * Based on the Xilinx, Altera PCIe driver
|
||||
+ *
|
||||
+ *
|
||||
+ * This program is free software: you can redistribute it and/or modify
|
||||
+ * it under the terms of the GNU General Public License as published by
|
||||
+ * the Free Software Foundation, either version 2 of the License, or
|
||||
+ * (at your option) any later version.
|
||||
+ */
|
||||
+
|
||||
+#include <linux/interrupt.h>
|
||||
+#include <linux/irq.h>
|
||||
+#include <linux/irqdomain.h>
|
||||
+#include <linux/kernel.h>
|
||||
+#include <linux/init.h>
|
||||
+#include <linux/msi.h>
|
||||
+#include <linux/of_address.h>
|
||||
+#include <linux/of_pci.h>
|
||||
+#include <linux/of_platform.h>
|
||||
+#include <linux/of_irq.h>
|
||||
+#include <linux/pci.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include "../pci.h"
|
||||
+
|
||||
+/* ECAM definitions */
|
||||
+#define ECAM_BUS_NUM_SHIFT 20
|
||||
+#define ECAM_DEV_NUM_SHIFT 12
|
||||
+
|
||||
+/* Number of MSI IRQs */
|
||||
+#define MICROSEMI_NUM_MSI_IRQS 32
|
||||
+
|
||||
+/* PCIe Bridge Phy and Controller Phy offsets */
|
||||
+#define PCIE0_BRIDGE_PHY_ADDR_OFFSET 0x03004000u
|
||||
+#define PCIE0_CRTL_PHY_ADDR_OFFSET 0x03006000u
|
||||
+
|
||||
+#define PCIE0_BRIDGE_ADDR 0x03004000u
|
||||
+#define PCIE0_CRTL_ADDR 0x03006000u
|
||||
+
|
||||
+#define PCIE1_BRIDGE_ADDR 0x00008000u
|
||||
+#define PCIE1_CRTL_ADDR 0x0000A000u
|
||||
+
|
||||
+/* PCIe LTSSM State reg */
|
||||
+#define LTSSM_STATE 0x5c
|
||||
+
|
||||
+/* PCIe LTSSM L0 state */
|
||||
+#define LTSSM_L0_STATE 0x10
|
||||
+
|
||||
+/* PCIe Controller Phy Regs */
|
||||
+#define SEC_ERROR_INT 0x28
|
||||
+#define SEC_ERROR_INT_MASK 0x2c
|
||||
+#define DED_ERROR_INT 0x30
|
||||
+#define DED_ERROR_INT_MASK 0x34
|
||||
+#define ECC_CONTROL 0x38
|
||||
+#define PCIE_EVENT_INT 0x14c
|
||||
+
|
||||
+/* PCIe Bridge Phy Regs */
|
||||
+#define IMASK_LOCAL 0x180
|
||||
+#define ISTATUS_LOCAL 0x184
|
||||
+#define IMASK_HOST 0x188
|
||||
+#define ISTATUS_HOST 0x18c
|
||||
+#define ISTATUS_MSI 0x194
|
||||
+#define PCIE_PCI_IDS_DW1 0x9c
|
||||
+
|
||||
+/* PCIe AXI slave table init defines */
|
||||
+#define ATR0_AXI4_SLV0_SRCADDR_PARAM 0x800u
|
||||
+#define ATR0_AXI4_SLV0_SRC_ADDR 0x804u
|
||||
+#define ATR0_AXI4_SLV0_TRSL_ADDR_LSB 0x808u
|
||||
+#define ATR0_AXI4_SLV0_TRSL_ADDR_UDW 0x80cu
|
||||
+#define ATR0_AXI4_SLV0_TRSL_PARAM 0x810u
|
||||
+
|
||||
+#define ATR1_AXI4_SLV0_SRCADDR_PARAM 0x820u
|
||||
+#define ATR1_AXI4_SLV0_SRC_ADDR 0x824u
|
||||
+#define ATR1_AXI4_SLV0_TRSL_ADDR_LSB 0x828u
|
||||
+#define ATR1_AXI4_SLV0_TRSL_ADDR_UDW 0x82cu
|
||||
+#define ATR1_AXI4_SLV0_TRSL_PARAM 0x830u
|
||||
+
|
||||
+/* PCIe Master table init defines */
|
||||
+#define ATR0_PCIE_WIN0_SRCADDR_PARAM 0x600u
|
||||
+
|
||||
+/* Translated ID */
|
||||
+#define PCIE_TX_RX_INTERFACE 0x00000000u
|
||||
+#define PCIE_CONFIG_INTERFACE 0x00000001u
|
||||
+
|
||||
+/* PCIe Config space MSI capability structure */
|
||||
+#define PCIE_ENABLE_MSI 0x10000000u
|
||||
+
|
||||
+/* MSI definitions */
|
||||
+#define MSI_MSG_ADDR 0x190u
|
||||
+#define MSI_ENABLE (0x01u << 16)
|
||||
+#define MSI_ENABLE_MULTI (MICROSEMI_NUM_MSI_IRQS << 20)
|
||||
+
|
||||
+/* MSI Capability Structure */
|
||||
+#define MSI_CAP_CTRL 0xE0u
|
||||
+#define MSI_MSG_ADDR_OFFSET 0xE4u
|
||||
+#define MSI_MSG_UPPER_ADDR_OFFEST 0xE8u
|
||||
+#define MSI_MSG_DATA 0xF0u
|
||||
+
|
||||
+
|
||||
+
|
||||
+/******************************************************************************/
|
||||
+/*Enable PCIe local*/
|
||||
+#define PCIE_LOCAL_INT_ENABLE 0xF000000u
|
||||
+
|
||||
+/******************************************************************************/
|
||||
+/* Clear PCIe interrupt events */
|
||||
+#define PCIE_EVENT_INT_DATA 0x00070007u
|
||||
+#define PCIE_ECC_DISABLE 0x0F000000u
|
||||
+#define PCIE_SEC_ERROR_INT_CLEAR 0x0000FFFFu
|
||||
+#define PCIE_DED_ERROR_INT_CLEAR 0x0000FFFFu
|
||||
+#define PCIE_ISTATUS_CLEAR 0xFFFFFFFFu
|
||||
+#define PCIE_CLEAR 0x00000000u
|
||||
+#define PCIE_SET 0x00000001u
|
||||
+
|
||||
+#define ROOT_PORT_ENABLE 0x00000001u
|
||||
+
|
||||
+#define NULL_POINTER 0x00000000u
|
||||
+
|
||||
+/*****************************************************************************/
|
||||
+/* PCIe Controller 0 */
|
||||
+#define PF_PCIE_CTRL_0 0u
|
||||
+/* PCIe Controller 1 */
|
||||
+#define PF_PCIE_CTRL_1 1u
|
||||
+
|
||||
+/* It indicates that the ATR table is enabled */
|
||||
+#define PF_PCIE_ATR_TABLE_ENABLE 1u
|
||||
+/* It indicates that the the ATR table is disabled */
|
||||
+#define PF_PCIE_ATR_TABLE_DISABLE 0u
|
||||
+
|
||||
+
|
||||
+/**
|
||||
+ * struct microsemi_pcie_port - PCIe port information
|
||||
+ * @reg_base: IO Mapped Register Base
|
||||
+ * @irq: Interrupt number
|
||||
+ * @root_busno: Root Bus number
|
||||
+ * @dev: Device pointer
|
||||
+ * @msi_domain: MSI IRQ domain pointer
|
||||
+ * @leg_domain: Legacy IRQ domain pointer
|
||||
+ * @resources: Bus Resources
|
||||
+ */
|
||||
+struct microsemi_pcie_port {
|
||||
+ struct platform_device *pdev;
|
||||
+ void __iomem *reg_base;
|
||||
+ void __iomem *reg_base_apb;
|
||||
+ void __iomem *reg_bridge_apb;
|
||||
+ void __iomem *reg_ctrl_apb;
|
||||
+ u32 irq;
|
||||
+ u8 root_busno;
|
||||
+ struct device *dev;
|
||||
+ struct irq_domain *msi_domain;
|
||||
+ struct irq_domain *leg_domain;
|
||||
+ struct list_head resources;
|
||||
+};
|
||||
+
|
||||
+static DECLARE_BITMAP(msi_irq_in_use, MICROSEMI_NUM_MSI_IRQS);
|
||||
+
|
||||
+static inline u32 pcie_read(struct microsemi_pcie_port *port, u32 reg)
|
||||
+{
|
||||
+ return readl(port->reg_base + reg);
|
||||
+}
|
||||
+
|
||||
+static inline void pcie_write(struct microsemi_pcie_port *port,
|
||||
+ u32 val, u32 reg)
|
||||
+{
|
||||
+ writel(val, port->reg_base + reg);
|
||||
+}
|
||||
+
|
||||
+static inline bool microsemi_pcie_link_up(struct microsemi_pcie_port *port)
|
||||
+{
|
||||
+ return (readl(port->reg_ctrl_apb + LTSSM_STATE)
|
||||
+ & LTSSM_L0_STATE) ? 1 : 0;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_pcie_valid_device - Check if a valid device is present on bus
|
||||
+ * @bus: PCI Bus structure
|
||||
+ * @devfn: device/function
|
||||
+ *
|
||||
+ * Return: 'true' on success and 'false' if invalid device is found
|
||||
+ */
|
||||
+static bool microsemi_pcie_valid_device(struct pci_bus *bus, unsigned int devfn)
|
||||
+{
|
||||
+ struct microsemi_pcie_port *port = bus->sysdata;
|
||||
+
|
||||
+ /* Check if link is up when trying to access downstream ports */
|
||||
+ if (bus->number != port->root_busno)
|
||||
+ if (!microsemi_pcie_link_up(port))
|
||||
+ return false;
|
||||
+
|
||||
+ /* Only one device down on each root port */
|
||||
+ if (bus->number == port->root_busno && devfn > 0)
|
||||
+ return false;
|
||||
+
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_pcie_map_bus - Get configuration base
|
||||
+ * @bus: PCI Bus structure
|
||||
+ * @devfn: Device/function
|
||||
+ * @where: Offset from base
|
||||
+ *
|
||||
+ * Return: Base address of the configuration space needed to be
|
||||
+ * accessed.
|
||||
+ */
|
||||
+static void __iomem *microsemi_pcie_map_bus(struct pci_bus *bus,
|
||||
+ unsigned int devfn, int where)
|
||||
+{
|
||||
+ struct microsemi_pcie_port *port = bus->sysdata;
|
||||
+ int relbus;
|
||||
+
|
||||
+
|
||||
+ if (!microsemi_pcie_valid_device(bus, devfn))
|
||||
+ return NULL;
|
||||
+
|
||||
+ relbus = (bus->number << ECAM_BUS_NUM_SHIFT) |
|
||||
+ (devfn << ECAM_DEV_NUM_SHIFT);
|
||||
+
|
||||
+
|
||||
+ return port->reg_base + relbus + where;
|
||||
+}
|
||||
+
|
||||
+/* PCIe operations */
|
||||
+static struct pci_ops microsemi_pcie_ops = {
|
||||
+ .map_bus = microsemi_pcie_map_bus,
|
||||
+ .read = pci_generic_config_read,
|
||||
+ .write = pci_generic_config_write,
|
||||
+};
|
||||
+
|
||||
+/* MSI functions */
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_pcie_destroy_msi - Free MSI number
|
||||
+ * @irq: IRQ to be freed
|
||||
+ */
|
||||
+static void microsemi_pcie_destroy_msi(unsigned int irq)
|
||||
+{
|
||||
+ struct msi_desc *msi;
|
||||
+ struct microsemi_pcie_port *port;
|
||||
+ struct irq_data *d = irq_get_irq_data(irq);
|
||||
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
|
||||
+
|
||||
+ if (!test_bit(hwirq, msi_irq_in_use)) {
|
||||
+ msi = irq_get_msi_desc(irq);
|
||||
+ port = msi_desc_to_pci_sysdata(msi);
|
||||
+ dev_err(port->dev, "Trying to free unused MSI#%d\n", irq);
|
||||
+ } else {
|
||||
+ clear_bit(hwirq, msi_irq_in_use);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_pcie_assign_msi - Allocate MSI number
|
||||
+ *
|
||||
+ * Return: A valid IRQ on success and error value on failure.
|
||||
+ */
|
||||
+static int microsemi_pcie_assign_msi(void)
|
||||
+{
|
||||
+ int pos;
|
||||
+
|
||||
+ pos = find_first_zero_bit(msi_irq_in_use, MICROSEMI_NUM_MSI_IRQS);
|
||||
+ if (pos < MICROSEMI_NUM_MSI_IRQS)
|
||||
+ set_bit(pos, msi_irq_in_use);
|
||||
+ else
|
||||
+ return -ENOSPC;
|
||||
+
|
||||
+ return pos;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_msi_teardown_irq - Destroy the MSI
|
||||
+ * @chip: MSI Chip descriptor
|
||||
+ * @irq: MSI IRQ to destroy
|
||||
+ */
|
||||
+static void microsemi_msi_teardown_irq(struct msi_controller *chip,
|
||||
+ unsigned int irq)
|
||||
+{
|
||||
+ microsemi_pcie_destroy_msi(irq);
|
||||
+ irq_dispose_mapping(irq);
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_pcie_msi_setup_irq - Setup MSI request
|
||||
+ * @chip: MSI chip pointer
|
||||
+ * @pdev: PCIe device pointer
|
||||
+ * @desc: MSI descriptor pointer
|
||||
+ *
|
||||
+ * Return: '0' on success and error value on failure
|
||||
+ */
|
||||
+static int microsemi_pcie_msi_setup_irq(struct msi_controller *chip,
|
||||
+ struct pci_dev *pdev,
|
||||
+ struct msi_desc *desc)
|
||||
+{
|
||||
+ struct microsemi_pcie_port *port = pdev->bus->sysdata;
|
||||
+ unsigned int irq;
|
||||
+ int hwirq;
|
||||
+ struct msi_msg msg;
|
||||
+
|
||||
+ hwirq = microsemi_pcie_assign_msi();
|
||||
+ if (hwirq < 0)
|
||||
+ return hwirq;
|
||||
+
|
||||
+ irq = irq_create_mapping(port->msi_domain, hwirq);
|
||||
+ if (!irq)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ irq_set_msi_desc(irq, desc);
|
||||
+
|
||||
+ msg.address_hi = 0;
|
||||
+ msg.address_lo = MSI_MSG_ADDR;
|
||||
+ msg.data = hwirq;
|
||||
+
|
||||
+ pci_write_msi_msg(irq, &msg);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/* MSI Chip Descriptor */
|
||||
+static struct msi_controller microsemi_pcie_msi_chip = {
|
||||
+ .setup_irq = microsemi_pcie_msi_setup_irq,
|
||||
+ .teardown_irq = microsemi_msi_teardown_irq,
|
||||
+};
|
||||
+
|
||||
+/* HW Interrupt Chip Descriptor */
|
||||
+static struct irq_chip microsemi_msi_irq_chip = {
|
||||
+ .name = "Microsemi PCIe MSI",
|
||||
+ .irq_enable = pci_msi_unmask_irq,
|
||||
+ .irq_disable = pci_msi_mask_irq,
|
||||
+ .irq_mask = pci_msi_mask_irq,
|
||||
+ .irq_unmask = pci_msi_unmask_irq,
|
||||
+};
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_pcie_msi_map - Set the handler for the MSI and mark IRQ as valid
|
||||
+ * @domain: IRQ domain
|
||||
+ * @irq: Virtual IRQ number
|
||||
+ * @hwirq: HW interrupt number
|
||||
+ *
|
||||
+ * Return: Always returns 0.
|
||||
+ */
|
||||
+static int microsemi_pcie_msi_map(struct irq_domain *domain, unsigned int irq,
|
||||
+ irq_hw_number_t hwirq)
|
||||
+{
|
||||
+ irq_set_chip_and_handler(irq,
|
||||
+ µsemi_msi_irq_chip, handle_simple_irq);
|
||||
+ irq_set_chip_data(irq, domain->host_data);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/* IRQ Domain operations */
|
||||
+static const struct irq_domain_ops msi_domain_ops = {
|
||||
+ .map = microsemi_pcie_msi_map,
|
||||
+};
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_pcie_enable_msi - Enable MSI support
|
||||
+ * @port: PCIe port information
|
||||
+ */
|
||||
+static void microsemi_pcie_enable_msi(struct microsemi_pcie_port *port)
|
||||
+{
|
||||
+ u32 cap_ctrl;
|
||||
+
|
||||
+ cap_ctrl = pcie_read(port, MSI_CAP_CTRL);
|
||||
+
|
||||
+ pcie_write(port, cap_ctrl | MSI_ENABLE_MULTI | MSI_ENABLE, MSI_CAP_CTRL);
|
||||
+ pcie_write(port, MSI_MSG_ADDR, MSI_MSG_ADDR_OFFSET);
|
||||
+}
|
||||
+
|
||||
+/* INTx Functions */
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_pcie_intx_map - Set the handler for the INTx and mark IRQ as valid
|
||||
+ * @domain: IRQ domain
|
||||
+ * @irq: Virtual IRQ number
|
||||
+ * @hwirq: HW interrupt number
|
||||
+ *
|
||||
+ * Return: Always returns 0.
|
||||
+ */
|
||||
+static int microsemi_pcie_intx_map(struct irq_domain *domain, unsigned int irq,
|
||||
+ irq_hw_number_t hwirq)
|
||||
+{
|
||||
+ irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq);
|
||||
+ irq_set_chip_data(irq, domain->host_data);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/* INTx IRQ Domain operations */
|
||||
+static const struct irq_domain_ops intx_domain_ops = {
|
||||
+ .map = microsemi_pcie_intx_map,
|
||||
+ .xlate = pci_irqd_intx_xlate,
|
||||
+};
|
||||
+
|
||||
+/* PCIe HW Functions */
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_pcie_intr_handler - Interrupt Service Handler
|
||||
+ * @irq: IRQ number
|
||||
+ * @data: PCIe port information
|
||||
+ *
|
||||
+ * Return: IRQ_HANDLED on success and IRQ_NONE on failure
|
||||
+ */
|
||||
+static irqreturn_t microsemi_pcie_intr_handler(int irq, void *data)
|
||||
+{
|
||||
+ struct microsemi_pcie_port *port = (struct microsemi_pcie_port *)data;
|
||||
+ struct device *dev = port->dev;
|
||||
+ unsigned long status;
|
||||
+ unsigned long msi;
|
||||
+ u32 bit;
|
||||
+ u32 virq;
|
||||
+
|
||||
+
|
||||
+ status = readl(port->reg_bridge_apb + ISTATUS_LOCAL);
|
||||
+
|
||||
+ status = (status >> 24) & 0x0f;
|
||||
+ for_each_set_bit(bit, &status, PCI_NUM_INTX) {
|
||||
+ /* clear interrupts */
|
||||
+ writel(1 << (bit+24),
|
||||
+ port->reg_bridge_apb + ISTATUS_LOCAL);
|
||||
+
|
||||
+ virq = irq_find_mapping(port->leg_domain, bit);
|
||||
+
|
||||
+ if (virq)
|
||||
+ generic_handle_irq(virq);
|
||||
+ else
|
||||
+ dev_err(dev, "unexpected IRQ, INT%d\n", bit);
|
||||
+ }
|
||||
+
|
||||
+ status = readl(port->reg_bridge_apb + ISTATUS_LOCAL);
|
||||
+ if ((status & 0x10000000) == 0x10000000) {
|
||||
+ writel((1 << 28), port->reg_bridge_apb + ISTATUS_LOCAL);
|
||||
+ msi = readl(port->reg_bridge_apb + ISTATUS_MSI);
|
||||
+ for_each_set_bit(bit, &msi, MICROSEMI_NUM_MSI_IRQS) {
|
||||
+ /* clear interrupts */
|
||||
+ writel((1 << bit),
|
||||
+ port->reg_bridge_apb + ISTATUS_MSI);
|
||||
+ virq = irq_find_mapping(port->msi_domain, bit);
|
||||
+ if (virq)
|
||||
+ generic_handle_irq(virq);
|
||||
+ else
|
||||
+ dev_err(dev, "unexpected IRQ, INT%d\n", bit);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return IRQ_HANDLED;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_pcie_init_irq_domain - Initialize IRQ domain
|
||||
+ * @port: PCIe port information
|
||||
+ *
|
||||
+ * Return: '0' on success and error value on failure
|
||||
+ */
|
||||
+static int microsemi_pcie_init_irq_domain(struct microsemi_pcie_port *port)
|
||||
+{
|
||||
+ struct device *dev = port->dev;
|
||||
+ struct device_node *node = dev->of_node;
|
||||
+ struct device_node *pcie_intc_node;
|
||||
+
|
||||
+ /* Setup INTx */
|
||||
+ pcie_intc_node = of_get_next_child(node, NULL);
|
||||
+ if (!pcie_intc_node) {
|
||||
+ dev_err(dev, "No PCIe Intc node found\n");
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+ dev_info(dev, "Intc node foundi %s\n", pcie_intc_node->name);
|
||||
+
|
||||
+ port->leg_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
|
||||
+ &intx_domain_ops,
|
||||
+ port);
|
||||
+ if (!port->leg_domain) {
|
||||
+ dev_err(dev, "Failed to get a INTx IRQ domain\n");
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+
|
||||
+ /* Setup MSI */
|
||||
+ if (IS_ENABLED(CONFIG_PCI_MSI)) {
|
||||
+ port->msi_domain = irq_domain_add_linear(node,
|
||||
+ MICROSEMI_NUM_MSI_IRQS,
|
||||
+ &msi_domain_ops,
|
||||
+ µsemi_pcie_msi_chip);
|
||||
+ if (!port->msi_domain) {
|
||||
+ dev_err(dev, "Failed to get a MSI IRQ domain\n");
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+ microsemi_pcie_enable_msi(port);
|
||||
+ }
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_pcie_init_port - Parse Device tree, Initialize hardware
|
||||
+ * @port: PCIe port information
|
||||
+ *
|
||||
+ * Return: '0' on success and error value on failure
|
||||
+ */
|
||||
+static int microsemi_pcie_init_port(struct microsemi_pcie_port *port)
|
||||
+{
|
||||
+ struct device *dev = port->dev;
|
||||
+ struct device_node *node = dev->of_node;
|
||||
+ struct of_pci_range_parser parser;
|
||||
+ struct of_pci_range range;
|
||||
+ struct resource regs;
|
||||
+ struct resource regs1;
|
||||
+ resource_size_t size;
|
||||
+ u32 bit, pf_bridge_id = 1;
|
||||
+ const char *type;
|
||||
+ int err;
|
||||
+
|
||||
+ type = of_get_property(node, "device_type", NULL);
|
||||
+ if (!type || strcmp(type, "pci")) {
|
||||
+ dev_err(dev, "invalid \"device_type\" %s\n", type);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ err = of_address_to_resource(node, 0, ®s);
|
||||
+ if (err) {
|
||||
+ dev_err(dev, "missing \"reg\" property\n");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+ port->reg_base = devm_pci_remap_cfg_resource(dev, ®s);
|
||||
+ if (IS_ERR(port->reg_base))
|
||||
+ return PTR_ERR(port->reg_base);
|
||||
+
|
||||
+
|
||||
+ err = of_address_to_resource(node, 1, ®s1);
|
||||
+ if (err) {
|
||||
+ dev_err(dev, "missing \"reg\" property\n");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ port->reg_base_apb = devm_ioremap_resource(dev, ®s1);
|
||||
+ if (IS_ERR(port->reg_base_apb))
|
||||
+ return PTR_ERR(port->reg_base_apb);
|
||||
+
|
||||
+ if (pf_bridge_id == 0) {
|
||||
+ port->reg_bridge_apb = port->reg_base_apb + PCIE0_BRIDGE_ADDR;
|
||||
+ port->reg_ctrl_apb = port->reg_base_apb + PCIE0_CRTL_ADDR;
|
||||
+ } else {
|
||||
+ port->reg_bridge_apb = port->reg_base_apb + PCIE1_BRIDGE_ADDR;
|
||||
+ port->reg_ctrl_apb = port->reg_base_apb + PCIE1_CRTL_ADDR;
|
||||
+ }
|
||||
+
|
||||
+ port->irq = irq_of_parse_and_map(node, 0);
|
||||
+
|
||||
+ err = devm_request_irq(dev, port->irq, microsemi_pcie_intr_handler,
|
||||
+ IRQF_SHARED | IRQF_NO_THREAD,
|
||||
+ "microsemi-pcie", port);
|
||||
+ if (err) {
|
||||
+ dev_err(dev, "unable to request irq %d\n", port->irq);
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ /* Clear and Disable interrupts */
|
||||
+
|
||||
+ writel(0x0f000000, port->reg_ctrl_apb + ECC_CONTROL);
|
||||
+ writel(0x00070007, port->reg_ctrl_apb + PCIE_EVENT_INT);
|
||||
+ writel(0x0000ffff, port->reg_ctrl_apb + SEC_ERROR_INT);
|
||||
+ writel(0x0000ffff, port->reg_ctrl_apb + SEC_ERROR_INT_MASK);
|
||||
+ writel(0x0000ffff, port->reg_ctrl_apb + DED_ERROR_INT);
|
||||
+ writel(0x0000ffff, port->reg_ctrl_apb + DED_ERROR_INT_MASK);
|
||||
+
|
||||
+ writel(0x00000000, port->reg_bridge_apb + IMASK_LOCAL);
|
||||
+ writel(0xffffffff, port->reg_bridge_apb + ISTATUS_LOCAL);
|
||||
+ writel(0x00000000, port->reg_bridge_apb + IMASK_HOST);
|
||||
+ writel(0xffffffff, port->reg_bridge_apb + ISTATUS_HOST);
|
||||
+
|
||||
+ dev_info(dev, "interrupt disabled\n");
|
||||
+
|
||||
+ /* Configure Address Translation Table 0 for pcie config space */
|
||||
+
|
||||
+ writel(PCIE_CONFIG_INTERFACE,
|
||||
+ port->reg_bridge_apb + ATR0_AXI4_SLV0_TRSL_PARAM);
|
||||
+
|
||||
+ size = resource_size(®s);
|
||||
+
|
||||
+ bit = find_first_bit((const unsigned long *)&size, 64) - 1;
|
||||
+
|
||||
+ writel((u32)regs.start | bit << 1 | 0x01,
|
||||
+ port->reg_bridge_apb + ATR0_AXI4_SLV0_SRCADDR_PARAM);
|
||||
+
|
||||
+// writel((u32)(regs.start >> 32),
|
||||
+// port->reg_bridge_apb + ATR0_AXI4_SLV0_SRC_ADDR);
|
||||
+
|
||||
+ writel((u32)regs.start,
|
||||
+ port->reg_bridge_apb + ATR0_AXI4_SLV0_TRSL_ADDR_LSB);
|
||||
+
|
||||
+// writel((u32)(regs.start >> 32),
|
||||
+// port->reg_bridge_apb + ATR0_AXI4_SLV0_TRSL_ADDR_UDW);
|
||||
+
|
||||
+
|
||||
+ if (of_pci_range_parser_init(&parser, node)) {
|
||||
+ dev_err(dev, "missing \"ranges\" property\n");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ for_each_of_pci_range(&parser, &range) {
|
||||
+ switch (range.flags & IORESOURCE_TYPE_BITS) {
|
||||
+ case IORESOURCE_MEM:
|
||||
+
|
||||
+ size = range.size;
|
||||
+ bit = find_first_bit((const unsigned long *)&size, 64) - 1;
|
||||
+
|
||||
+ /* Configure Address Translation Table 1 for pcie mem space */
|
||||
+
|
||||
+ writel(PCIE_TX_RX_INTERFACE,
|
||||
+ port->reg_bridge_apb + ATR1_AXI4_SLV0_TRSL_PARAM);
|
||||
+
|
||||
+ writel((u32)range.cpu_addr | bit << 1 | 0x01,
|
||||
+ port->reg_bridge_apb + ATR1_AXI4_SLV0_SRCADDR_PARAM);
|
||||
+
|
||||
+// writel((u32)(range.cpu_addr >> 32),
|
||||
+// port->reg_bridge_apb + ATR1_AXI4_SLV0_SRC_ADDR);
|
||||
+
|
||||
+ writel((u32)range.pci_addr,
|
||||
+ port->reg_bridge_apb + ATR1_AXI4_SLV0_TRSL_ADDR_LSB);
|
||||
+
|
||||
+// writel((u32)(range.pci_addr >> 32),
|
||||
+// port->reg_bridge_apb + ATR1_AXI4_SLV0_TRSL_ADDR_UDW);
|
||||
+
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ writel(readl(port->reg_bridge_apb + ATR0_PCIE_WIN0_SRCADDR_PARAM)
|
||||
+ | 0x3E,
|
||||
+ port->reg_bridge_apb + ATR0_PCIE_WIN0_SRCADDR_PARAM);
|
||||
+
|
||||
+ writel(0, port->reg_bridge_apb + 0x604);
|
||||
+
|
||||
+ writel((readl(port->reg_bridge_apb + PCIE_PCI_IDS_DW1) & 0xffff)
|
||||
+ | (PCI_CLASS_BRIDGE_PCI << 16),
|
||||
+ port->reg_bridge_apb + PCIE_PCI_IDS_DW1);
|
||||
+
|
||||
+ pcie_write(port, 0x00ff0100, 0x18);
|
||||
+
|
||||
+ /* Enable interrupts */
|
||||
+ writel(PCIE_ENABLE_MSI | PCIE_LOCAL_INT_ENABLE,
|
||||
+ port->reg_bridge_apb + IMASK_LOCAL);
|
||||
+
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * microsemi_pcie_probe - Probe function
|
||||
+ * @pdev: Platform device pointer
|
||||
+ *
|
||||
+ * Return: '0' on success and error value on failure
|
||||
+ */
|
||||
+static int microsemi_pcie_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct microsemi_pcie_port *port;
|
||||
+ struct pci_bus *bus, *child;
|
||||
+ struct pci_host_bridge *bridge;
|
||||
+ int err;
|
||||
+ resource_size_t iobase = 0;
|
||||
+ LIST_HEAD(res);
|
||||
+
|
||||
+ pr_err("%s In \n", __func__);
|
||||
+ if (!dev->of_node)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ bridge = devm_pci_alloc_host_bridge(dev, sizeof(*port));
|
||||
+ if (!bridge)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ port = pci_host_bridge_priv(bridge);
|
||||
+
|
||||
+ port->dev = dev;
|
||||
+ port->pdev = pdev;
|
||||
+
|
||||
+ err = microsemi_pcie_init_port(port);
|
||||
+ if (err) {
|
||||
+ dev_err(dev, "Pcie port initialization failed\n");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ err = microsemi_pcie_init_irq_domain(port);
|
||||
+ if (err) {
|
||||
+ dev_err(dev, "Failed creating IRQ Domain\n");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+ err = devm_of_pci_get_host_bridge_resources(dev, 0, 0xff, &res,
|
||||
+ &iobase);
|
||||
+ if (err) {
|
||||
+ dev_err(dev, "Getting bridge resources failed\n");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+ err = devm_request_pci_bus_resources(dev, &res);
|
||||
+ if (err)
|
||||
+ goto error;
|
||||
+
|
||||
+
|
||||
+ list_splice_init(&res, &bridge->windows);
|
||||
+ bridge->dev.parent = dev;
|
||||
+ bridge->sysdata = port;
|
||||
+ bridge->busnr = 0;
|
||||
+ bridge->ops = µsemi_pcie_ops;
|
||||
+ bridge->map_irq = of_irq_parse_and_map_pci;
|
||||
+ bridge->swizzle_irq = pci_common_swizzle;
|
||||
+
|
||||
+#ifdef CONFIG_PCI_MSI
|
||||
+ microsemi_pcie_msi_chip.dev = dev;
|
||||
+ bridge->msi = µsemi_pcie_msi_chip;
|
||||
+#endif
|
||||
+ err = pci_scan_root_bus_bridge(bridge);
|
||||
+ dev_info(dev, "pci_scan_root_bus_bridge done\n");
|
||||
+ if (err < 0)
|
||||
+ goto error;
|
||||
+
|
||||
+ bus = bridge->bus;
|
||||
+
|
||||
+ pci_assign_unassigned_bus_resources(bus);
|
||||
+ list_for_each_entry(child, &bus->children, node)
|
||||
+ pcie_bus_configure_settings(child);
|
||||
+ pci_bus_add_devices(bus);
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+error:
|
||||
+ pci_free_resource_list(&res);
|
||||
+ return err;
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id microsemi_pcie_of_match[] = {
|
||||
+ { .compatible = "ms-pf,axi-pcie-host", },
|
||||
+ {}
|
||||
+};
|
||||
+
|
||||
+static struct platform_driver microsemi_pcie_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "microsemi-pcie",
|
||||
+ .of_match_table = microsemi_pcie_of_match,
|
||||
+ .suppress_bind_attrs = true,
|
||||
+ },
|
||||
+ .probe = microsemi_pcie_probe,
|
||||
+};
|
||||
+builtin_platform_driver(microsemi_pcie_driver);
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@@ -6,3 +6,16 @@ LINUX_VERSION ?= "4.19"
|
||||
|
||||
BRANCH = "riscv-linux-4.19"
|
||||
SRCREV = "998246b5d00db61bb6449aa81a1df145e5aadf6b"
|
||||
|
||||
SRC_URI_append_freedom-u540 = " \
|
||||
file://0001-spi-sifive-support-SiFive-SPI-controller-in-Quad-Mod.patch \
|
||||
file://0002-spi-nor-add-support-for-is25wp-32-64-128-256.patch \
|
||||
file://0003-serial-sifive-initial-driver-from-Paul-Walmsley.patch \
|
||||
file://0004-gemgxl-mgmt-implement-clock-switch-for-GEM-tx_clk.patch \
|
||||
file://0005-u54-prci-driver-for-core-U54-clocks.patch \
|
||||
file://0006-u54-prci-driver-for-core-U54-clocks.patch \
|
||||
file://0007-gpio-sifive-support-GPIO-on-SiFive-SoCs.patch \
|
||||
file://0008-pwm-sifive-add-a-driver-for-SiFive-SoC-PWM.patch \
|
||||
file://0009-RISC-V-Networking-fix-Hack.patch \
|
||||
file://0010-pcie-microsemi-added-support-for-the-Vera-board-root.patch \
|
||||
"
|
||||
|
||||
Reference in New Issue
Block a user