linux: freedom-u540: Add the HiFive Unleashed drivers

Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
This commit is contained in:
Alistair Francis
2018-12-10 10:44:35 -08:00
committed by Khem Raj
parent 7a0d9f2e64
commit 7b8c06236f
11 changed files with 3994 additions and 0 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,
+ &microsemi_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,
+ &microsemi_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, &regs);
+ if (err) {
+ dev_err(dev, "missing \"reg\" property\n");
+ return err;
+ }
+
+ port->reg_base = devm_pci_remap_cfg_resource(dev, &regs);
+ if (IS_ERR(port->reg_base))
+ return PTR_ERR(port->reg_base);
+
+
+ err = of_address_to_resource(node, 1, &regs1);
+ if (err) {
+ dev_err(dev, "missing \"reg\" property\n");
+ return err;
+ }
+
+
+ port->reg_base_apb = devm_ioremap_resource(dev, &regs1);
+ 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(&regs);
+
+ 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 = &microsemi_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 = &microsemi_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

View File

@@ -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 \
"