summaryrefslogtreecommitdiffstats
path: root/arch/powerpc/platforms/pasemi
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /arch/powerpc/platforms/pasemi
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'arch/powerpc/platforms/pasemi')
-rw-r--r--arch/powerpc/platforms/pasemi/Kconfig51
-rw-r--r--arch/powerpc/platforms/pasemi/Makefile4
-rw-r--r--arch/powerpc/platforms/pasemi/dma_lib.c621
-rw-r--r--arch/powerpc/platforms/pasemi/gpio_mdio.c327
-rw-r--r--arch/powerpc/platforms/pasemi/idle.c93
-rw-r--r--arch/powerpc/platforms/pasemi/iommu.c267
-rw-r--r--arch/powerpc/platforms/pasemi/misc.c87
-rw-r--r--arch/powerpc/platforms/pasemi/msi.c162
-rw-r--r--arch/powerpc/platforms/pasemi/pasemi.h36
-rw-r--r--arch/powerpc/platforms/pasemi/pci.c294
-rw-r--r--arch/powerpc/platforms/pasemi/powersave.S76
-rw-r--r--arch/powerpc/platforms/pasemi/setup.c456
-rw-r--r--arch/powerpc/platforms/pasemi/time.c18
13 files changed, 2492 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/pasemi/Kconfig b/arch/powerpc/platforms/pasemi/Kconfig
new file mode 100644
index 0000000000..85ae18ddd9
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/Kconfig
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: GPL-2.0
+config PPC_PASEMI
+ depends on PPC64 && PPC_BOOK3S && CPU_BIG_ENDIAN
+ bool "PA Semi SoC-based platforms"
+ select MPIC
+ select FORCE_PCI
+ select PPC_UDBG_16550
+ select PPC_64S_HASH_MMU
+ select PPC_HASH_MMU_NATIVE
+ select MPIC_BROKEN_REGREAD
+ help
+ This option enables support for PA Semi's PWRficient line
+ of SoC processors, including PA6T-1682M
+
+menu "PA Semi PWRficient options"
+ depends on PPC_PASEMI
+
+config PPC_PASEMI_NEMO
+ bool "Nemo motherboard Support"
+ depends on PPC_PASEMI
+ select PPC_I8259
+ help
+ This option enables support for the 'Nemo' motherboard
+ used in A-Eons's Amigaone X1000. This consists of some
+ device tree patches and workarounds for the SB600 South
+ Bridge that provides SATA/USB/Audio.
+
+config PPC_PASEMI_IOMMU
+ bool "PA Semi IOMMU support"
+ depends on PPC_PASEMI
+ help
+ IOMMU support for PA Semi PWRficient
+
+config PPC_PASEMI_IOMMU_DMA_FORCE
+ bool "Force DMA engine to use IOMMU"
+ depends on PPC_PASEMI_IOMMU
+ help
+ This option forces the use of the IOMMU also for the
+ DMA engine. Otherwise the kernel will use it only when
+ running under a hypervisor.
+
+ If in doubt, say "N".
+
+config PPC_PASEMI_MDIO
+ depends on PHYLIB
+ tristate "MDIO support via GPIO"
+ default y
+ help
+ Driver for MDIO via GPIO on PWRficient platforms
+
+endmenu
diff --git a/arch/powerpc/platforms/pasemi/Makefile b/arch/powerpc/platforms/pasemi/Makefile
new file mode 100644
index 0000000000..d2ce954a50
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-y += setup.o pci.o time.o idle.o powersave.o iommu.o dma_lib.o misc.o
+obj-$(CONFIG_PPC_PASEMI_MDIO) += gpio_mdio.o
+obj-$(CONFIG_PCI_MSI) += msi.o
diff --git a/arch/powerpc/platforms/pasemi/dma_lib.c b/arch/powerpc/platforms/pasemi/dma_lib.c
new file mode 100644
index 0000000000..1be1f18f6f
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/dma_lib.c
@@ -0,0 +1,621 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2006-2007 PA Semi, Inc
+ *
+ * Common functions for DMA access on PA Semi PWRficient
+ */
+
+#include <linux/kernel.h>
+#include <linux/export.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/sched.h>
+
+#include <asm/pasemi_dma.h>
+
+#define MAX_TXCH 64
+#define MAX_RXCH 64
+#define MAX_FLAGS 64
+#define MAX_FUN 8
+
+static struct pasdma_status *dma_status;
+
+static void __iomem *iob_regs;
+static void __iomem *mac_regs[6];
+static void __iomem *dma_regs;
+
+static int base_hw_irq;
+
+static int num_txch, num_rxch;
+
+static struct pci_dev *dma_pdev;
+
+/* Bitmaps to handle allocation of channels */
+
+static DECLARE_BITMAP(txch_free, MAX_TXCH);
+static DECLARE_BITMAP(rxch_free, MAX_RXCH);
+static DECLARE_BITMAP(flags_free, MAX_FLAGS);
+static DECLARE_BITMAP(fun_free, MAX_FUN);
+
+/* pasemi_read_iob_reg - read IOB register
+ * @reg: Register to read (offset into PCI CFG space)
+ */
+unsigned int pasemi_read_iob_reg(unsigned int reg)
+{
+ return in_le32(iob_regs+reg);
+}
+EXPORT_SYMBOL(pasemi_read_iob_reg);
+
+/* pasemi_write_iob_reg - write IOB register
+ * @reg: Register to write to (offset into PCI CFG space)
+ * @val: Value to write
+ */
+void pasemi_write_iob_reg(unsigned int reg, unsigned int val)
+{
+ out_le32(iob_regs+reg, val);
+}
+EXPORT_SYMBOL(pasemi_write_iob_reg);
+
+/* pasemi_read_mac_reg - read MAC register
+ * @intf: MAC interface
+ * @reg: Register to read (offset into PCI CFG space)
+ */
+unsigned int pasemi_read_mac_reg(int intf, unsigned int reg)
+{
+ return in_le32(mac_regs[intf]+reg);
+}
+EXPORT_SYMBOL(pasemi_read_mac_reg);
+
+/* pasemi_write_mac_reg - write MAC register
+ * @intf: MAC interface
+ * @reg: Register to write to (offset into PCI CFG space)
+ * @val: Value to write
+ */
+void pasemi_write_mac_reg(int intf, unsigned int reg, unsigned int val)
+{
+ out_le32(mac_regs[intf]+reg, val);
+}
+EXPORT_SYMBOL(pasemi_write_mac_reg);
+
+/* pasemi_read_dma_reg - read DMA register
+ * @reg: Register to read (offset into PCI CFG space)
+ */
+unsigned int pasemi_read_dma_reg(unsigned int reg)
+{
+ return in_le32(dma_regs+reg);
+}
+EXPORT_SYMBOL(pasemi_read_dma_reg);
+
+/* pasemi_write_dma_reg - write DMA register
+ * @reg: Register to write to (offset into PCI CFG space)
+ * @val: Value to write
+ */
+void pasemi_write_dma_reg(unsigned int reg, unsigned int val)
+{
+ out_le32(dma_regs+reg, val);
+}
+EXPORT_SYMBOL(pasemi_write_dma_reg);
+
+static int pasemi_alloc_tx_chan(enum pasemi_dmachan_type type)
+{
+ int bit;
+ int start, limit;
+
+ switch (type & (TXCHAN_EVT0|TXCHAN_EVT1)) {
+ case TXCHAN_EVT0:
+ start = 0;
+ limit = 10;
+ break;
+ case TXCHAN_EVT1:
+ start = 10;
+ limit = MAX_TXCH;
+ break;
+ default:
+ start = 0;
+ limit = MAX_TXCH;
+ break;
+ }
+retry:
+ bit = find_next_bit(txch_free, MAX_TXCH, start);
+ if (bit >= limit)
+ return -ENOSPC;
+ if (!test_and_clear_bit(bit, txch_free))
+ goto retry;
+
+ return bit;
+}
+
+static void pasemi_free_tx_chan(int chan)
+{
+ BUG_ON(test_bit(chan, txch_free));
+ set_bit(chan, txch_free);
+}
+
+static int pasemi_alloc_rx_chan(void)
+{
+ int bit;
+retry:
+ bit = find_first_bit(rxch_free, MAX_RXCH);
+ if (bit >= MAX_TXCH)
+ return -ENOSPC;
+ if (!test_and_clear_bit(bit, rxch_free))
+ goto retry;
+
+ return bit;
+}
+
+static void pasemi_free_rx_chan(int chan)
+{
+ BUG_ON(test_bit(chan, rxch_free));
+ set_bit(chan, rxch_free);
+}
+
+/* pasemi_dma_alloc_chan - Allocate a DMA channel
+ * @type: Type of channel to allocate
+ * @total_size: Total size of structure to allocate (to allow for more
+ * room behind the structure to be used by the client)
+ * @offset: Offset in bytes from start of the total structure to the beginning
+ * of struct pasemi_dmachan. Needed when struct pasemi_dmachan is
+ * not the first member of the client structure.
+ *
+ * pasemi_dma_alloc_chan allocates a DMA channel for use by a client. The
+ * type argument specifies whether it's a RX or TX channel, and in the case
+ * of TX channels which group it needs to belong to (if any).
+ *
+ * Returns a pointer to the total structure allocated on success, NULL
+ * on failure.
+ */
+void *pasemi_dma_alloc_chan(enum pasemi_dmachan_type type,
+ int total_size, int offset)
+{
+ void *buf;
+ struct pasemi_dmachan *chan;
+ int chno;
+
+ BUG_ON(total_size < sizeof(struct pasemi_dmachan));
+
+ buf = kzalloc(total_size, GFP_KERNEL);
+
+ if (!buf)
+ return NULL;
+ chan = buf + offset;
+
+ chan->priv = buf;
+
+ switch (type & (TXCHAN|RXCHAN)) {
+ case RXCHAN:
+ chno = pasemi_alloc_rx_chan();
+ chan->chno = chno;
+ chan->irq = irq_create_mapping(NULL,
+ base_hw_irq + num_txch + chno);
+ chan->status = &dma_status->rx_sta[chno];
+ break;
+ case TXCHAN:
+ chno = pasemi_alloc_tx_chan(type);
+ chan->chno = chno;
+ chan->irq = irq_create_mapping(NULL, base_hw_irq + chno);
+ chan->status = &dma_status->tx_sta[chno];
+ break;
+ }
+
+ chan->chan_type = type;
+
+ return chan;
+}
+EXPORT_SYMBOL(pasemi_dma_alloc_chan);
+
+/* pasemi_dma_free_chan - Free a previously allocated channel
+ * @chan: Channel to free
+ *
+ * Frees a previously allocated channel. It will also deallocate any
+ * descriptor ring associated with the channel, if allocated.
+ */
+void pasemi_dma_free_chan(struct pasemi_dmachan *chan)
+{
+ if (chan->ring_virt)
+ pasemi_dma_free_ring(chan);
+
+ switch (chan->chan_type & (RXCHAN|TXCHAN)) {
+ case RXCHAN:
+ pasemi_free_rx_chan(chan->chno);
+ break;
+ case TXCHAN:
+ pasemi_free_tx_chan(chan->chno);
+ break;
+ }
+
+ kfree(chan->priv);
+}
+EXPORT_SYMBOL(pasemi_dma_free_chan);
+
+/* pasemi_dma_alloc_ring - Allocate descriptor ring for a channel
+ * @chan: Channel for which to allocate
+ * @ring_size: Ring size in 64-bit (8-byte) words
+ *
+ * Allocate a descriptor ring for a channel. Returns 0 on success, errno
+ * on failure. The passed in struct pasemi_dmachan is updated with the
+ * virtual and DMA addresses of the ring.
+ */
+int pasemi_dma_alloc_ring(struct pasemi_dmachan *chan, int ring_size)
+{
+ BUG_ON(chan->ring_virt);
+
+ chan->ring_size = ring_size;
+
+ chan->ring_virt = dma_alloc_coherent(&dma_pdev->dev,
+ ring_size * sizeof(u64),
+ &chan->ring_dma, GFP_KERNEL);
+
+ if (!chan->ring_virt)
+ return -ENOMEM;
+
+ return 0;
+}
+EXPORT_SYMBOL(pasemi_dma_alloc_ring);
+
+/* pasemi_dma_free_ring - Free an allocated descriptor ring for a channel
+ * @chan: Channel for which to free the descriptor ring
+ *
+ * Frees a previously allocated descriptor ring for a channel.
+ */
+void pasemi_dma_free_ring(struct pasemi_dmachan *chan)
+{
+ BUG_ON(!chan->ring_virt);
+
+ dma_free_coherent(&dma_pdev->dev, chan->ring_size * sizeof(u64),
+ chan->ring_virt, chan->ring_dma);
+ chan->ring_virt = NULL;
+ chan->ring_size = 0;
+ chan->ring_dma = 0;
+}
+EXPORT_SYMBOL(pasemi_dma_free_ring);
+
+/* pasemi_dma_start_chan - Start a DMA channel
+ * @chan: Channel to start
+ * @cmdsta: Additional CCMDSTA/TCMDSTA bits to write
+ *
+ * Enables (starts) a DMA channel with optional additional arguments.
+ */
+void pasemi_dma_start_chan(const struct pasemi_dmachan *chan, const u32 cmdsta)
+{
+ if (chan->chan_type == RXCHAN)
+ pasemi_write_dma_reg(PAS_DMA_RXCHAN_CCMDSTA(chan->chno),
+ cmdsta | PAS_DMA_RXCHAN_CCMDSTA_EN);
+ else
+ pasemi_write_dma_reg(PAS_DMA_TXCHAN_TCMDSTA(chan->chno),
+ cmdsta | PAS_DMA_TXCHAN_TCMDSTA_EN);
+}
+EXPORT_SYMBOL(pasemi_dma_start_chan);
+
+/* pasemi_dma_stop_chan - Stop a DMA channel
+ * @chan: Channel to stop
+ *
+ * Stops (disables) a DMA channel. This is done by setting the ST bit in the
+ * CMDSTA register and waiting on the ACT (active) bit to clear, then
+ * finally disabling the whole channel.
+ *
+ * This function will only try for a short while for the channel to stop, if
+ * it doesn't it will return failure.
+ *
+ * Returns 1 on success, 0 on failure.
+ */
+#define MAX_RETRIES 5000
+int pasemi_dma_stop_chan(const struct pasemi_dmachan *chan)
+{
+ int reg, retries;
+ u32 sta;
+
+ if (chan->chan_type == RXCHAN) {
+ reg = PAS_DMA_RXCHAN_CCMDSTA(chan->chno);
+ pasemi_write_dma_reg(reg, PAS_DMA_RXCHAN_CCMDSTA_ST);
+ for (retries = 0; retries < MAX_RETRIES; retries++) {
+ sta = pasemi_read_dma_reg(reg);
+ if (!(sta & PAS_DMA_RXCHAN_CCMDSTA_ACT)) {
+ pasemi_write_dma_reg(reg, 0);
+ return 1;
+ }
+ cond_resched();
+ }
+ } else {
+ reg = PAS_DMA_TXCHAN_TCMDSTA(chan->chno);
+ pasemi_write_dma_reg(reg, PAS_DMA_TXCHAN_TCMDSTA_ST);
+ for (retries = 0; retries < MAX_RETRIES; retries++) {
+ sta = pasemi_read_dma_reg(reg);
+ if (!(sta & PAS_DMA_TXCHAN_TCMDSTA_ACT)) {
+ pasemi_write_dma_reg(reg, 0);
+ return 1;
+ }
+ cond_resched();
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(pasemi_dma_stop_chan);
+
+/* pasemi_dma_alloc_buf - Allocate a buffer to use for DMA
+ * @chan: Channel to allocate for
+ * @size: Size of buffer in bytes
+ * @handle: DMA handle
+ *
+ * Allocate a buffer to be used by the DMA engine for read/write,
+ * similar to dma_alloc_coherent().
+ *
+ * Returns the virtual address of the buffer, or NULL in case of failure.
+ */
+void *pasemi_dma_alloc_buf(struct pasemi_dmachan *chan, int size,
+ dma_addr_t *handle)
+{
+ return dma_alloc_coherent(&dma_pdev->dev, size, handle, GFP_KERNEL);
+}
+EXPORT_SYMBOL(pasemi_dma_alloc_buf);
+
+/* pasemi_dma_free_buf - Free a buffer used for DMA
+ * @chan: Channel the buffer was allocated for
+ * @size: Size of buffer in bytes
+ * @handle: DMA handle
+ *
+ * Frees a previously allocated buffer.
+ */
+void pasemi_dma_free_buf(struct pasemi_dmachan *chan, int size,
+ dma_addr_t *handle)
+{
+ dma_free_coherent(&dma_pdev->dev, size, handle, GFP_KERNEL);
+}
+EXPORT_SYMBOL(pasemi_dma_free_buf);
+
+/* pasemi_dma_alloc_flag - Allocate a flag (event) for channel synchronization
+ *
+ * Allocates a flag for use with channel synchronization (event descriptors).
+ * Returns allocated flag (0-63), < 0 on error.
+ */
+int pasemi_dma_alloc_flag(void)
+{
+ int bit;
+
+retry:
+ bit = find_first_bit(flags_free, MAX_FLAGS);
+ if (bit >= MAX_FLAGS)
+ return -ENOSPC;
+ if (!test_and_clear_bit(bit, flags_free))
+ goto retry;
+
+ return bit;
+}
+EXPORT_SYMBOL(pasemi_dma_alloc_flag);
+
+
+/* pasemi_dma_free_flag - Deallocates a flag (event)
+ * @flag: Flag number to deallocate
+ *
+ * Frees up a flag so it can be reused for other purposes.
+ */
+void pasemi_dma_free_flag(int flag)
+{
+ BUG_ON(test_bit(flag, flags_free));
+ BUG_ON(flag >= MAX_FLAGS);
+ set_bit(flag, flags_free);
+}
+EXPORT_SYMBOL(pasemi_dma_free_flag);
+
+
+/* pasemi_dma_set_flag - Sets a flag (event) to 1
+ * @flag: Flag number to set active
+ *
+ * Sets the flag provided to 1.
+ */
+void pasemi_dma_set_flag(int flag)
+{
+ BUG_ON(flag >= MAX_FLAGS);
+ if (flag < 32)
+ pasemi_write_dma_reg(PAS_DMA_TXF_SFLG0, 1 << flag);
+ else
+ pasemi_write_dma_reg(PAS_DMA_TXF_SFLG1, 1 << flag);
+}
+EXPORT_SYMBOL(pasemi_dma_set_flag);
+
+/* pasemi_dma_clear_flag - Sets a flag (event) to 0
+ * @flag: Flag number to set inactive
+ *
+ * Sets the flag provided to 0.
+ */
+void pasemi_dma_clear_flag(int flag)
+{
+ BUG_ON(flag >= MAX_FLAGS);
+ if (flag < 32)
+ pasemi_write_dma_reg(PAS_DMA_TXF_CFLG0, 1 << flag);
+ else
+ pasemi_write_dma_reg(PAS_DMA_TXF_CFLG1, 1 << flag);
+}
+EXPORT_SYMBOL(pasemi_dma_clear_flag);
+
+/* pasemi_dma_alloc_fun - Allocate a function engine
+ *
+ * Allocates a function engine to use for crypto/checksum offload
+ * Returns allocated engine (0-8), < 0 on error.
+ */
+int pasemi_dma_alloc_fun(void)
+{
+ int bit;
+
+retry:
+ bit = find_first_bit(fun_free, MAX_FLAGS);
+ if (bit >= MAX_FLAGS)
+ return -ENOSPC;
+ if (!test_and_clear_bit(bit, fun_free))
+ goto retry;
+
+ return bit;
+}
+EXPORT_SYMBOL(pasemi_dma_alloc_fun);
+
+
+/* pasemi_dma_free_fun - Deallocates a function engine
+ * @flag: Engine number to deallocate
+ *
+ * Frees up a function engine so it can be used for other purposes.
+ */
+void pasemi_dma_free_fun(int fun)
+{
+ BUG_ON(test_bit(fun, fun_free));
+ BUG_ON(fun >= MAX_FLAGS);
+ set_bit(fun, fun_free);
+}
+EXPORT_SYMBOL(pasemi_dma_free_fun);
+
+
+static void *map_onedev(struct pci_dev *p, int index)
+{
+ struct device_node *dn;
+ void __iomem *ret;
+
+ dn = pci_device_to_OF_node(p);
+ if (!dn)
+ goto fallback;
+
+ ret = of_iomap(dn, index);
+ if (!ret)
+ goto fallback;
+
+ return ret;
+fallback:
+ /* This is hardcoded and ugly, but we have some firmware versions
+ * that don't provide the register space in the device tree. Luckily
+ * they are at well-known locations so we can just do the math here.
+ */
+ return ioremap(0xe0000000 + (p->devfn << 12), 0x2000);
+}
+
+/* pasemi_dma_init - Initialize the PA Semi DMA library
+ *
+ * This function initializes the DMA library. It must be called before
+ * any other function in the library.
+ *
+ * Returns 0 on success, errno on failure.
+ */
+int pasemi_dma_init(void)
+{
+ static DEFINE_SPINLOCK(init_lock);
+ struct pci_dev *iob_pdev;
+ struct pci_dev *pdev;
+ struct resource res;
+ struct device_node *dn;
+ int i, intf, err = 0;
+ unsigned long timeout;
+ u32 tmp;
+
+ if (!machine_is(pasemi))
+ return -ENODEV;
+
+ spin_lock(&init_lock);
+
+ /* Make sure we haven't already initialized */
+ if (dma_pdev)
+ goto out;
+
+ iob_pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa001, NULL);
+ if (!iob_pdev) {
+ BUG();
+ pr_warn("Can't find I/O Bridge\n");
+ err = -ENODEV;
+ goto out;
+ }
+ iob_regs = map_onedev(iob_pdev, 0);
+
+ dma_pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa007, NULL);
+ if (!dma_pdev) {
+ BUG();
+ pr_warn("Can't find DMA controller\n");
+ err = -ENODEV;
+ goto out;
+ }
+ dma_regs = map_onedev(dma_pdev, 0);
+ base_hw_irq = virq_to_hw(dma_pdev->irq);
+
+ pci_read_config_dword(dma_pdev, PAS_DMA_CAP_TXCH, &tmp);
+ num_txch = (tmp & PAS_DMA_CAP_TXCH_TCHN_M) >> PAS_DMA_CAP_TXCH_TCHN_S;
+
+ pci_read_config_dword(dma_pdev, PAS_DMA_CAP_RXCH, &tmp);
+ num_rxch = (tmp & PAS_DMA_CAP_RXCH_RCHN_M) >> PAS_DMA_CAP_RXCH_RCHN_S;
+
+ intf = 0;
+ for (pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa006, NULL);
+ pdev;
+ pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa006, pdev))
+ mac_regs[intf++] = map_onedev(pdev, 0);
+
+ pci_dev_put(pdev);
+
+ for (pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa005, NULL);
+ pdev;
+ pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa005, pdev))
+ mac_regs[intf++] = map_onedev(pdev, 0);
+
+ pci_dev_put(pdev);
+
+ dn = pci_device_to_OF_node(iob_pdev);
+ if (dn)
+ err = of_address_to_resource(dn, 1, &res);
+ if (!dn || err) {
+ /* Fallback for old firmware */
+ res.start = 0xfd800000;
+ res.end = res.start + 0x1000;
+ }
+ dma_status = ioremap_cache(res.start, resource_size(&res));
+ pci_dev_put(iob_pdev);
+
+ for (i = 0; i < MAX_TXCH; i++)
+ __set_bit(i, txch_free);
+
+ for (i = 0; i < MAX_RXCH; i++)
+ __set_bit(i, rxch_free);
+
+ timeout = jiffies + HZ;
+ pasemi_write_dma_reg(PAS_DMA_COM_RXCMD, 0);
+ while (pasemi_read_dma_reg(PAS_DMA_COM_RXSTA) & 1) {
+ if (time_after(jiffies, timeout)) {
+ pr_warn("Warning: Could not disable RX section\n");
+ break;
+ }
+ }
+
+ timeout = jiffies + HZ;
+ pasemi_write_dma_reg(PAS_DMA_COM_TXCMD, 0);
+ while (pasemi_read_dma_reg(PAS_DMA_COM_TXSTA) & 1) {
+ if (time_after(jiffies, timeout)) {
+ pr_warn("Warning: Could not disable TX section\n");
+ break;
+ }
+ }
+
+ /* setup resource allocations for the different DMA sections */
+ tmp = pasemi_read_dma_reg(PAS_DMA_COM_CFG);
+ pasemi_write_dma_reg(PAS_DMA_COM_CFG, tmp | 0x18000000);
+
+ /* enable tx section */
+ pasemi_write_dma_reg(PAS_DMA_COM_TXCMD, PAS_DMA_COM_TXCMD_EN);
+
+ /* enable rx section */
+ pasemi_write_dma_reg(PAS_DMA_COM_RXCMD, PAS_DMA_COM_RXCMD_EN);
+
+ for (i = 0; i < MAX_FLAGS; i++)
+ __set_bit(i, flags_free);
+
+ for (i = 0; i < MAX_FUN; i++)
+ __set_bit(i, fun_free);
+
+ /* clear all status flags */
+ pasemi_write_dma_reg(PAS_DMA_TXF_CFLG0, 0xffffffff);
+ pasemi_write_dma_reg(PAS_DMA_TXF_CFLG1, 0xffffffff);
+
+ pr_info("PA Semi PWRficient DMA library initialized "
+ "(%d tx, %d rx channels)\n", num_txch, num_rxch);
+
+out:
+ spin_unlock(&init_lock);
+ return err;
+}
+EXPORT_SYMBOL(pasemi_dma_init);
diff --git a/arch/powerpc/platforms/pasemi/gpio_mdio.c b/arch/powerpc/platforms/pasemi/gpio_mdio.c
new file mode 100644
index 0000000000..fd130fe7a6
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/gpio_mdio.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2006-2007 PA Semi, Inc
+ *
+ * Author: Olof Johansson, PA Semi
+ *
+ * Maintained by: Olof Johansson <olof@lixom.net>
+ *
+ * Based on drivers/net/fs_enet/mii-bitbang.c.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/phy.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+#include <linux/platform_device.h>
+
+#define DELAY 1
+
+static void __iomem *gpio_regs;
+
+struct gpio_priv {
+ int mdc_pin;
+ int mdio_pin;
+};
+
+#define MDC_PIN(bus) (((struct gpio_priv *)bus->priv)->mdc_pin)
+#define MDIO_PIN(bus) (((struct gpio_priv *)bus->priv)->mdio_pin)
+
+static inline void mdio_lo(struct mii_bus *bus)
+{
+ out_le32(gpio_regs+0x10, 1 << MDIO_PIN(bus));
+}
+
+static inline void mdio_hi(struct mii_bus *bus)
+{
+ out_le32(gpio_regs, 1 << MDIO_PIN(bus));
+}
+
+static inline void mdc_lo(struct mii_bus *bus)
+{
+ out_le32(gpio_regs+0x10, 1 << MDC_PIN(bus));
+}
+
+static inline void mdc_hi(struct mii_bus *bus)
+{
+ out_le32(gpio_regs, 1 << MDC_PIN(bus));
+}
+
+static inline void mdio_active(struct mii_bus *bus)
+{
+ out_le32(gpio_regs+0x20, (1 << MDC_PIN(bus)) | (1 << MDIO_PIN(bus)));
+}
+
+static inline void mdio_tristate(struct mii_bus *bus)
+{
+ out_le32(gpio_regs+0x30, (1 << MDIO_PIN(bus)));
+}
+
+static inline int mdio_read(struct mii_bus *bus)
+{
+ return !!(in_le32(gpio_regs+0x40) & (1 << MDIO_PIN(bus)));
+}
+
+static void clock_out(struct mii_bus *bus, int bit)
+{
+ if (bit)
+ mdio_hi(bus);
+ else
+ mdio_lo(bus);
+ udelay(DELAY);
+ mdc_hi(bus);
+ udelay(DELAY);
+ mdc_lo(bus);
+}
+
+/* Utility to send the preamble, address, and register (common to read and write). */
+static void bitbang_pre(struct mii_bus *bus, int read, u8 addr, u8 reg)
+{
+ int i;
+
+ /* CFE uses a really long preamble (40 bits). We'll do the same. */
+ mdio_active(bus);
+ for (i = 0; i < 40; i++) {
+ clock_out(bus, 1);
+ }
+
+ /* send the start bit (01) and the read opcode (10) or write (10) */
+ clock_out(bus, 0);
+ clock_out(bus, 1);
+
+ clock_out(bus, read);
+ clock_out(bus, !read);
+
+ /* send the PHY address */
+ for (i = 0; i < 5; i++) {
+ clock_out(bus, (addr & 0x10) != 0);
+ addr <<= 1;
+ }
+
+ /* send the register address */
+ for (i = 0; i < 5; i++) {
+ clock_out(bus, (reg & 0x10) != 0);
+ reg <<= 1;
+ }
+}
+
+static int gpio_mdio_read(struct mii_bus *bus, int phy_id, int location)
+{
+ u16 rdreg;
+ int ret, i;
+ u8 addr = phy_id & 0xff;
+ u8 reg = location & 0xff;
+
+ bitbang_pre(bus, 1, addr, reg);
+
+ /* tri-state our MDIO I/O pin so we can read */
+ mdio_tristate(bus);
+ udelay(DELAY);
+ mdc_hi(bus);
+ udelay(DELAY);
+ mdc_lo(bus);
+
+ /* read 16 bits of register data, MSB first */
+ rdreg = 0;
+ for (i = 0; i < 16; i++) {
+ mdc_lo(bus);
+ udelay(DELAY);
+ mdc_hi(bus);
+ udelay(DELAY);
+ mdc_lo(bus);
+ udelay(DELAY);
+ rdreg <<= 1;
+ rdreg |= mdio_read(bus);
+ }
+
+ mdc_hi(bus);
+ udelay(DELAY);
+ mdc_lo(bus);
+ udelay(DELAY);
+
+ ret = rdreg;
+
+ return ret;
+}
+
+static int gpio_mdio_write(struct mii_bus *bus, int phy_id, int location, u16 val)
+{
+ int i;
+
+ u8 addr = phy_id & 0xff;
+ u8 reg = location & 0xff;
+ u16 value = val & 0xffff;
+
+ bitbang_pre(bus, 0, addr, reg);
+
+ /* send the turnaround (10) */
+ mdc_lo(bus);
+ mdio_hi(bus);
+ udelay(DELAY);
+ mdc_hi(bus);
+ udelay(DELAY);
+ mdc_lo(bus);
+ mdio_lo(bus);
+ udelay(DELAY);
+ mdc_hi(bus);
+ udelay(DELAY);
+
+ /* write 16 bits of register data, MSB first */
+ for (i = 0; i < 16; i++) {
+ mdc_lo(bus);
+ if (value & 0x8000)
+ mdio_hi(bus);
+ else
+ mdio_lo(bus);
+ udelay(DELAY);
+ mdc_hi(bus);
+ udelay(DELAY);
+ value <<= 1;
+ }
+
+ /*
+ * Tri-state the MDIO line.
+ */
+ mdio_tristate(bus);
+ mdc_lo(bus);
+ udelay(DELAY);
+ mdc_hi(bus);
+ udelay(DELAY);
+ return 0;
+}
+
+static int gpio_mdio_reset(struct mii_bus *bus)
+{
+ /*nothing here - dunno how to reset it*/
+ return 0;
+}
+
+
+static int gpio_mdio_probe(struct platform_device *ofdev)
+{
+ struct device *dev = &ofdev->dev;
+ struct device_node *np = ofdev->dev.of_node;
+ struct mii_bus *new_bus;
+ struct gpio_priv *priv;
+ const unsigned int *prop;
+ int err;
+
+ err = -ENOMEM;
+ priv = kzalloc(sizeof(struct gpio_priv), GFP_KERNEL);
+ if (!priv)
+ goto out;
+
+ new_bus = mdiobus_alloc();
+
+ if (!new_bus)
+ goto out_free_priv;
+
+ new_bus->name = "pasemi gpio mdio bus";
+ new_bus->read = &gpio_mdio_read;
+ new_bus->write = &gpio_mdio_write;
+ new_bus->reset = &gpio_mdio_reset;
+
+ prop = of_get_property(np, "reg", NULL);
+ snprintf(new_bus->id, MII_BUS_ID_SIZE, "%x", *prop);
+ new_bus->priv = priv;
+
+ prop = of_get_property(np, "mdc-pin", NULL);
+ priv->mdc_pin = *prop;
+
+ prop = of_get_property(np, "mdio-pin", NULL);
+ priv->mdio_pin = *prop;
+
+ new_bus->parent = dev;
+ dev_set_drvdata(dev, new_bus);
+
+ err = of_mdiobus_register(new_bus, np);
+
+ if (err != 0) {
+ pr_err("%s: Cannot register as MDIO bus, err %d\n",
+ new_bus->name, err);
+ goto out_free_irq;
+ }
+
+ return 0;
+
+out_free_irq:
+ kfree(new_bus);
+out_free_priv:
+ kfree(priv);
+out:
+ return err;
+}
+
+
+static int gpio_mdio_remove(struct platform_device *dev)
+{
+ struct mii_bus *bus = dev_get_drvdata(&dev->dev);
+
+ mdiobus_unregister(bus);
+
+ dev_set_drvdata(&dev->dev, NULL);
+
+ kfree(bus->priv);
+ bus->priv = NULL;
+ mdiobus_free(bus);
+
+ return 0;
+}
+
+static const struct of_device_id gpio_mdio_match[] =
+{
+ {
+ .compatible = "gpio-mdio",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, gpio_mdio_match);
+
+static struct platform_driver gpio_mdio_driver =
+{
+ .probe = gpio_mdio_probe,
+ .remove = gpio_mdio_remove,
+ .driver = {
+ .name = "gpio-mdio-bitbang",
+ .of_match_table = gpio_mdio_match,
+ },
+};
+
+static int __init gpio_mdio_init(void)
+{
+ struct device_node *np;
+
+ np = of_find_compatible_node(NULL, NULL, "1682m-gpio");
+ if (!np)
+ np = of_find_compatible_node(NULL, NULL,
+ "pasemi,pwrficient-gpio");
+ if (!np)
+ return -ENODEV;
+ gpio_regs = of_iomap(np, 0);
+ of_node_put(np);
+
+ if (!gpio_regs)
+ return -ENODEV;
+
+ return platform_driver_register(&gpio_mdio_driver);
+}
+module_init(gpio_mdio_init);
+
+static void __exit gpio_mdio_exit(void)
+{
+ platform_driver_unregister(&gpio_mdio_driver);
+ if (gpio_regs)
+ iounmap(gpio_regs);
+}
+module_exit(gpio_mdio_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Olof Johansson <olof@lixom.net>");
+MODULE_DESCRIPTION("Driver for MDIO over GPIO on PA Semi PWRficient-based boards");
diff --git a/arch/powerpc/platforms/pasemi/idle.c b/arch/powerpc/platforms/pasemi/idle.c
new file mode 100644
index 0000000000..6087c70ed2
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/idle.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2006-2007 PA Semi, Inc
+ *
+ * Maintained by: Olof Johansson <olof@lixom.net>
+ */
+
+#undef DEBUG
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/irq.h>
+
+#include <asm/machdep.h>
+#include <asm/reg.h>
+#include <asm/smp.h>
+
+#include "pasemi.h"
+
+struct sleep_mode {
+ char *name;
+ void (*entry)(void);
+};
+
+static struct sleep_mode modes[] = {
+ { .name = "spin", .entry = &idle_spin },
+ { .name = "doze", .entry = &idle_doze },
+};
+
+static int current_mode = 0;
+
+static int pasemi_system_reset_exception(struct pt_regs *regs)
+{
+ /* If we were woken up from power savings, we need to return
+ * to the calling function, since nip is not saved across
+ * all modes.
+ */
+
+ if (regs->msr & SRR1_WAKEMASK)
+ regs_set_return_ip(regs, regs->link);
+
+ switch (regs->msr & SRR1_WAKEMASK) {
+ case SRR1_WAKEDEC:
+ set_dec(1);
+ break;
+ case SRR1_WAKEEE:
+ /*
+ * Handle these when interrupts get re-enabled and we take
+ * them as regular exceptions. We are in an NMI context
+ * and can't handle these here.
+ */
+ break;
+ default:
+ /* do system reset */
+ return 0;
+ }
+
+ /* Set higher astate since we come out of power savings at 0 */
+ restore_astate(hard_smp_processor_id());
+
+ /* everything handled */
+ regs_set_recoverable(regs);
+ return 1;
+}
+
+static int __init pasemi_idle_init(void)
+{
+#ifndef CONFIG_PPC_PASEMI_CPUFREQ
+ pr_warn("No cpufreq driver, powersavings modes disabled\n");
+ current_mode = 0;
+#endif
+
+ ppc_md.system_reset_exception = pasemi_system_reset_exception;
+ ppc_md.power_save = modes[current_mode].entry;
+ pr_info("Using PA6T idle loop (%s)\n", modes[current_mode].name);
+
+ return 0;
+}
+machine_late_initcall(pasemi, pasemi_idle_init);
+
+static int __init idle_param(char *p)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(modes); i++) {
+ if (!strcmp(modes[i].name, p)) {
+ current_mode = i;
+ break;
+ }
+ }
+ return 0;
+}
+
+early_param("idle", idle_param);
diff --git a/arch/powerpc/platforms/pasemi/iommu.c b/arch/powerpc/platforms/pasemi/iommu.c
new file mode 100644
index 0000000000..375487cba8
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/iommu.c
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2005-2008, PA Semi, Inc
+ *
+ * Maintained by: Olof Johansson <olof@lixom.net>
+ */
+
+#undef DEBUG
+
+#include <linux/memblock.h>
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include <linux/pci.h>
+#include <linux/of.h>
+#include <asm/iommu.h>
+#include <asm/machdep.h>
+#include <asm/firmware.h>
+
+#include "pasemi.h"
+
+#define IOBMAP_PAGE_SHIFT 12
+#define IOBMAP_PAGE_SIZE (1 << IOBMAP_PAGE_SHIFT)
+#define IOBMAP_PAGE_MASK (IOBMAP_PAGE_SIZE - 1)
+
+#define IOB_BASE 0xe0000000
+#define IOB_SIZE 0x3000
+/* Configuration registers */
+#define IOBCAP_REG 0x40
+#define IOBCOM_REG 0x100
+/* Enable IOB address translation */
+#define IOBCOM_ATEN 0x00000100
+
+/* Address decode configuration register */
+#define IOB_AD_REG 0x14c
+/* IOBCOM_AD_REG fields */
+#define IOB_AD_VGPRT 0x00000e00
+#define IOB_AD_VGAEN 0x00000100
+/* Direct mapping settings */
+#define IOB_AD_MPSEL_MASK 0x00000030
+#define IOB_AD_MPSEL_B38 0x00000000
+#define IOB_AD_MPSEL_B40 0x00000010
+#define IOB_AD_MPSEL_B42 0x00000020
+/* Translation window size / enable */
+#define IOB_AD_TRNG_MASK 0x00000003
+#define IOB_AD_TRNG_256M 0x00000000
+#define IOB_AD_TRNG_2G 0x00000001
+#define IOB_AD_TRNG_128G 0x00000003
+
+#define IOB_TABLEBASE_REG 0x154
+
+/* Base of the 64 4-byte L1 registers */
+#define IOB_XLT_L1_REGBASE 0x2b00
+
+/* Register to invalidate TLB entries */
+#define IOB_AT_INVAL_TLB_REG 0x2d00
+
+/* The top two bits of the level 1 entry contains valid and type flags */
+#define IOBMAP_L1E_V 0x40000000
+#define IOBMAP_L1E_V_B 0x80000000
+
+/* For big page entries, the bottom two bits contains flags */
+#define IOBMAP_L1E_BIG_CACHED 0x00000002
+#define IOBMAP_L1E_BIG_PRIORITY 0x00000001
+
+/* For regular level 2 entries, top 2 bits contain valid and cache flags */
+#define IOBMAP_L2E_V 0x80000000
+#define IOBMAP_L2E_V_CACHED 0xc0000000
+
+static void __iomem *iob;
+static u32 iob_l1_emptyval;
+static u32 iob_l2_emptyval;
+static u32 *iob_l2_base;
+
+static struct iommu_table iommu_table_iobmap;
+static int iommu_table_iobmap_inited;
+
+static int iobmap_build(struct iommu_table *tbl, long index,
+ long npages, unsigned long uaddr,
+ enum dma_data_direction direction,
+ unsigned long attrs)
+{
+ u32 *ip;
+ u32 rpn;
+ unsigned long bus_addr;
+
+ pr_debug("iobmap: build at: %lx, %lx, addr: %lx\n", index, npages, uaddr);
+
+ bus_addr = (tbl->it_offset + index) << IOBMAP_PAGE_SHIFT;
+
+ ip = ((u32 *)tbl->it_base) + index;
+
+ while (npages--) {
+ rpn = __pa(uaddr) >> IOBMAP_PAGE_SHIFT;
+
+ *(ip++) = IOBMAP_L2E_V | rpn;
+ /* invalidate tlb, can be optimized more */
+ out_le32(iob+IOB_AT_INVAL_TLB_REG, bus_addr >> 14);
+
+ uaddr += IOBMAP_PAGE_SIZE;
+ bus_addr += IOBMAP_PAGE_SIZE;
+ }
+ return 0;
+}
+
+
+static void iobmap_free(struct iommu_table *tbl, long index,
+ long npages)
+{
+ u32 *ip;
+ unsigned long bus_addr;
+
+ pr_debug("iobmap: free at: %lx, %lx\n", index, npages);
+
+ bus_addr = (tbl->it_offset + index) << IOBMAP_PAGE_SHIFT;
+
+ ip = ((u32 *)tbl->it_base) + index;
+
+ while (npages--) {
+ *(ip++) = iob_l2_emptyval;
+ /* invalidate tlb, can be optimized more */
+ out_le32(iob+IOB_AT_INVAL_TLB_REG, bus_addr >> 14);
+ bus_addr += IOBMAP_PAGE_SIZE;
+ }
+}
+
+static struct iommu_table_ops iommu_table_iobmap_ops = {
+ .set = iobmap_build,
+ .clear = iobmap_free
+};
+
+static void iommu_table_iobmap_setup(void)
+{
+ pr_debug(" -> %s\n", __func__);
+ iommu_table_iobmap.it_busno = 0;
+ iommu_table_iobmap.it_offset = 0;
+ iommu_table_iobmap.it_page_shift = IOBMAP_PAGE_SHIFT;
+
+ /* it_size is in number of entries */
+ iommu_table_iobmap.it_size =
+ 0x80000000 >> iommu_table_iobmap.it_page_shift;
+
+ /* Initialize the common IOMMU code */
+ iommu_table_iobmap.it_base = (unsigned long)iob_l2_base;
+ iommu_table_iobmap.it_index = 0;
+ /* XXXOJN tune this to avoid IOB cache invals.
+ * Should probably be 8 (64 bytes)
+ */
+ iommu_table_iobmap.it_blocksize = 4;
+ iommu_table_iobmap.it_ops = &iommu_table_iobmap_ops;
+ if (!iommu_init_table(&iommu_table_iobmap, 0, 0, 0))
+ panic("Failed to initialize iommu table");
+
+ pr_debug(" <- %s\n", __func__);
+}
+
+
+
+static void pci_dma_bus_setup_pasemi(struct pci_bus *bus)
+{
+ pr_debug("pci_dma_bus_setup, bus %p, bus->self %p\n", bus, bus->self);
+
+ if (!iommu_table_iobmap_inited) {
+ iommu_table_iobmap_inited = 1;
+ iommu_table_iobmap_setup();
+ }
+}
+
+
+static void pci_dma_dev_setup_pasemi(struct pci_dev *dev)
+{
+ pr_debug("pci_dma_dev_setup, dev %p (%s)\n", dev, pci_name(dev));
+
+#if !defined(CONFIG_PPC_PASEMI_IOMMU_DMA_FORCE)
+ /* For non-LPAR environment, don't translate anything for the DMA
+ * engine. The exception to this is if the user has enabled
+ * CONFIG_PPC_PASEMI_IOMMU_DMA_FORCE at build time.
+ */
+ if (dev->vendor == 0x1959 && dev->device == 0xa007 &&
+ !firmware_has_feature(FW_FEATURE_LPAR)) {
+ dev->dev.dma_ops = NULL;
+ /*
+ * Set the coherent DMA mask to prevent the iommu
+ * being used unnecessarily
+ */
+ dev->dev.coherent_dma_mask = DMA_BIT_MASK(44);
+ return;
+ }
+#endif
+
+ set_iommu_table_base(&dev->dev, &iommu_table_iobmap);
+}
+
+static int __init iob_init(struct device_node *dn)
+{
+ unsigned long tmp;
+ u32 regword;
+ int i;
+
+ pr_debug(" -> %s\n", __func__);
+
+ /* For 2G space, 8x64 pages (2^21 bytes) is max total l2 size */
+ iob_l2_base = memblock_alloc_try_nid_raw(1UL << 21, 1UL << 21,
+ MEMBLOCK_LOW_LIMIT, 0x80000000,
+ NUMA_NO_NODE);
+ if (!iob_l2_base)
+ panic("%s: Failed to allocate %lu bytes align=0x%lx max_addr=%x\n",
+ __func__, 1UL << 21, 1UL << 21, 0x80000000);
+
+ pr_info("IOBMAP L2 allocated at: %p\n", iob_l2_base);
+
+ /* Allocate a spare page to map all invalid IOTLB pages. */
+ tmp = memblock_phys_alloc(IOBMAP_PAGE_SIZE, IOBMAP_PAGE_SIZE);
+ if (!tmp)
+ panic("IOBMAP: Cannot allocate spare page!");
+ /* Empty l1 is marked invalid */
+ iob_l1_emptyval = 0;
+ /* Empty l2 is mapped to dummy page */
+ iob_l2_emptyval = IOBMAP_L2E_V | (tmp >> IOBMAP_PAGE_SHIFT);
+
+ iob = ioremap(IOB_BASE, IOB_SIZE);
+ if (!iob)
+ panic("IOBMAP: Cannot map registers!");
+
+ /* setup direct mapping of the L1 entries */
+ for (i = 0; i < 64; i++) {
+ /* Each L1 covers 32MB, i.e. 8K entries = 32K of ram */
+ regword = IOBMAP_L1E_V | (__pa(iob_l2_base + i*0x2000) >> 12);
+ out_le32(iob+IOB_XLT_L1_REGBASE+i*4, regword);
+ }
+
+ /* set 2GB translation window, based at 0 */
+ regword = in_le32(iob+IOB_AD_REG);
+ regword &= ~IOB_AD_TRNG_MASK;
+ regword |= IOB_AD_TRNG_2G;
+ out_le32(iob+IOB_AD_REG, regword);
+
+ /* Enable translation */
+ regword = in_le32(iob+IOBCOM_REG);
+ regword |= IOBCOM_ATEN;
+ out_le32(iob+IOBCOM_REG, regword);
+
+ pr_debug(" <- %s\n", __func__);
+
+ return 0;
+}
+
+
+/* These are called very early. */
+void __init iommu_init_early_pasemi(void)
+{
+ int iommu_off;
+
+#ifndef CONFIG_PPC_PASEMI_IOMMU
+ iommu_off = 1;
+#else
+ iommu_off = of_chosen &&
+ of_property_read_bool(of_chosen, "linux,iommu-off");
+#endif
+ if (iommu_off)
+ return;
+
+ iob_init(NULL);
+
+ pasemi_pci_controller_ops.dma_dev_setup = pci_dma_dev_setup_pasemi;
+ pasemi_pci_controller_ops.dma_bus_setup = pci_dma_bus_setup_pasemi;
+ set_pci_dma_ops(&dma_iommu_ops);
+}
diff --git a/arch/powerpc/platforms/pasemi/misc.c b/arch/powerpc/platforms/pasemi/misc.c
new file mode 100644
index 0000000000..9e9a7e4628
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/misc.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 PA Semi, Inc
+ *
+ * Parts based on arch/powerpc/sysdev/fsl_soc.c:
+ *
+ * 2006 (c) MontaVista Software, Inc.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/i2c.h>
+
+#ifdef CONFIG_I2C_BOARDINFO
+/* The below is from fsl_soc.c. It's copied because since there are no
+ * official bus bindings at this time it doesn't make sense to share across
+ * the platforms, even though they happen to be common.
+ */
+struct i2c_driver_device {
+ char *of_device;
+ char *i2c_type;
+};
+
+static struct i2c_driver_device i2c_devices[] __initdata = {
+ {"dallas,ds1338", "ds1338"},
+};
+
+static int __init find_i2c_driver(struct device_node *node,
+ struct i2c_board_info *info)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(i2c_devices); i++) {
+ if (!of_device_is_compatible(node, i2c_devices[i].of_device))
+ continue;
+ if (strscpy(info->type, i2c_devices[i].i2c_type, I2C_NAME_SIZE) < 0)
+ return -ENOMEM;
+ return 0;
+ }
+ return -ENODEV;
+}
+
+static int __init pasemi_register_i2c_devices(void)
+{
+ struct pci_dev *pdev;
+ struct device_node *adap_node;
+ struct device_node *node;
+
+ pdev = NULL;
+ while ((pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa003, pdev))) {
+ adap_node = pci_device_to_OF_node(pdev);
+
+ if (!adap_node)
+ continue;
+
+ for_each_child_of_node(adap_node, node) {
+ struct i2c_board_info info = {};
+ const u32 *addr;
+ int len;
+
+ addr = of_get_property(node, "reg", &len);
+ if (!addr || len < sizeof(int) ||
+ *addr > (1 << 10) - 1) {
+ pr_warn("pasemi_register_i2c_devices: invalid i2c device entry\n");
+ continue;
+ }
+
+ info.irq = irq_of_parse_and_map(node, 0);
+ if (!info.irq)
+ info.irq = -1;
+
+ if (find_i2c_driver(node, &info) < 0)
+ continue;
+
+ info.addr = *addr;
+
+ i2c_register_board_info(PCI_FUNC(pdev->devfn), &info,
+ 1);
+ }
+ }
+ return 0;
+}
+device_initcall(pasemi_register_i2c_devices);
+#endif
diff --git a/arch/powerpc/platforms/pasemi/msi.c b/arch/powerpc/platforms/pasemi/msi.c
new file mode 100644
index 0000000000..166c97fff1
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/msi.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2007, Olof Johansson, PA Semi
+ *
+ * Based on arch/powerpc/sysdev/mpic_u3msi.c:
+ *
+ * Copyright 2006, Segher Boessenkool, IBM Corporation.
+ * Copyright 2006-2007, Michael Ellerman, IBM Corporation.
+ */
+
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/msi.h>
+#include <asm/mpic.h>
+#include <asm/hw_irq.h>
+#include <asm/ppc-pci.h>
+#include <asm/msi_bitmap.h>
+
+#include <sysdev/mpic.h>
+
+/* Allocate 16 interrupts per device, to give an alignment of 16,
+ * since that's the size of the grouping w.r.t. affinity. If someone
+ * needs more than 32 MSI's down the road we'll have to rethink this,
+ * but it should be OK for now.
+ */
+#define ALLOC_CHUNK 16
+
+#define PASEMI_MSI_ADDR 0xfc080000
+
+/* A bit ugly, can we get this from the pci_dev somehow? */
+static struct mpic *msi_mpic;
+
+
+static void mpic_pasemi_msi_mask_irq(struct irq_data *data)
+{
+ pr_debug("mpic_pasemi_msi_mask_irq %d\n", data->irq);
+ pci_msi_mask_irq(data);
+ mpic_mask_irq(data);
+}
+
+static void mpic_pasemi_msi_unmask_irq(struct irq_data *data)
+{
+ pr_debug("mpic_pasemi_msi_unmask_irq %d\n", data->irq);
+ mpic_unmask_irq(data);
+ pci_msi_unmask_irq(data);
+}
+
+static struct irq_chip mpic_pasemi_msi_chip = {
+ .irq_shutdown = mpic_pasemi_msi_mask_irq,
+ .irq_mask = mpic_pasemi_msi_mask_irq,
+ .irq_unmask = mpic_pasemi_msi_unmask_irq,
+ .irq_eoi = mpic_end_irq,
+ .irq_set_type = mpic_set_irq_type,
+ .irq_set_affinity = mpic_set_affinity,
+ .name = "PASEMI-MSI",
+};
+
+static void pasemi_msi_teardown_msi_irqs(struct pci_dev *pdev)
+{
+ struct msi_desc *entry;
+ irq_hw_number_t hwirq;
+
+ pr_debug("pasemi_msi_teardown_msi_irqs, pdev %p\n", pdev);
+
+ msi_for_each_desc(entry, &pdev->dev, MSI_DESC_ASSOCIATED) {
+ hwirq = virq_to_hw(entry->irq);
+ irq_set_msi_desc(entry->irq, NULL);
+ irq_dispose_mapping(entry->irq);
+ entry->irq = 0;
+ msi_bitmap_free_hwirqs(&msi_mpic->msi_bitmap, hwirq, ALLOC_CHUNK);
+ }
+}
+
+static int pasemi_msi_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type)
+{
+ unsigned int virq;
+ struct msi_desc *entry;
+ struct msi_msg msg;
+ int hwirq;
+
+ if (type == PCI_CAP_ID_MSIX)
+ pr_debug("pasemi_msi: MSI-X untested, trying anyway\n");
+ pr_debug("pasemi_msi_setup_msi_irqs, pdev %p nvec %d type %d\n",
+ pdev, nvec, type);
+
+ msg.address_hi = 0;
+ msg.address_lo = PASEMI_MSI_ADDR;
+
+ msi_for_each_desc(entry, &pdev->dev, MSI_DESC_NOTASSOCIATED) {
+ /* Allocate 16 interrupts for now, since that's the grouping for
+ * affinity. This can be changed later if it turns out 32 is too
+ * few MSIs for someone, but restrictions will apply to how the
+ * sources can be changed independently.
+ */
+ hwirq = msi_bitmap_alloc_hwirqs(&msi_mpic->msi_bitmap,
+ ALLOC_CHUNK);
+ if (hwirq < 0) {
+ pr_debug("pasemi_msi: failed allocating hwirq\n");
+ return hwirq;
+ }
+
+ virq = irq_create_mapping(msi_mpic->irqhost, hwirq);
+ if (!virq) {
+ pr_debug("pasemi_msi: failed mapping hwirq 0x%x\n",
+ hwirq);
+ msi_bitmap_free_hwirqs(&msi_mpic->msi_bitmap, hwirq,
+ ALLOC_CHUNK);
+ return -ENOSPC;
+ }
+
+ /* Vector on MSI is really an offset, the hardware adds
+ * it to the value written at the magic address. So set
+ * it to 0 to remain sane.
+ */
+ mpic_set_vector(virq, 0);
+
+ irq_set_msi_desc(virq, entry);
+ irq_set_chip(virq, &mpic_pasemi_msi_chip);
+ irq_set_irq_type(virq, IRQ_TYPE_EDGE_RISING);
+
+ pr_debug("pasemi_msi: allocated virq 0x%x (hw 0x%x) " \
+ "addr 0x%x\n", virq, hwirq, msg.address_lo);
+
+ /* Likewise, the device writes [0...511] into the target
+ * register to generate MSI [512...1023]
+ */
+ msg.data = hwirq-0x200;
+ pci_write_msi_msg(virq, &msg);
+ }
+
+ return 0;
+}
+
+int __init mpic_pasemi_msi_init(struct mpic *mpic)
+{
+ int rc;
+ struct pci_controller *phb;
+ struct device_node *of_node;
+
+ of_node = irq_domain_get_of_node(mpic->irqhost);
+ if (!of_node ||
+ !of_device_is_compatible(of_node,
+ "pasemi,pwrficient-openpic"))
+ return -ENODEV;
+
+ rc = mpic_msi_init_allocator(mpic);
+ if (rc) {
+ pr_debug("pasemi_msi: Error allocating bitmap!\n");
+ return rc;
+ }
+
+ pr_debug("pasemi_msi: Registering PA Semi MPIC MSI callbacks\n");
+
+ msi_mpic = mpic;
+ list_for_each_entry(phb, &hose_list, list_node) {
+ WARN_ON(phb->controller_ops.setup_msi_irqs);
+ phb->controller_ops.setup_msi_irqs = pasemi_msi_setup_msi_irqs;
+ phb->controller_ops.teardown_msi_irqs = pasemi_msi_teardown_msi_irqs;
+ }
+
+ return 0;
+}
diff --git a/arch/powerpc/platforms/pasemi/pasemi.h b/arch/powerpc/platforms/pasemi/pasemi.h
new file mode 100644
index 0000000000..018c30665e
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/pasemi.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _PASEMI_PASEMI_H
+#define _PASEMI_PASEMI_H
+
+extern time64_t pas_get_boot_time(void);
+extern void pas_pci_init(void);
+struct pci_dev;
+extern void pas_pci_irq_fixup(struct pci_dev *dev);
+extern void pas_pci_dma_dev_setup(struct pci_dev *dev);
+
+void __iomem *__init pasemi_pci_getcfgaddr(struct pci_dev *dev, int offset);
+
+extern void __init pasemi_map_registers(void);
+
+/* Power savings modes, implemented in asm */
+extern void idle_spin(void);
+extern void idle_doze(void);
+
+/* Restore astate to last set */
+#ifdef CONFIG_PPC_PASEMI_CPUFREQ
+extern int check_astate(void);
+extern void restore_astate(int cpu);
+#else
+static inline int check_astate(void)
+{
+ /* Always return >0 so we never power save */
+ return 1;
+}
+static inline void restore_astate(int cpu)
+{
+}
+#endif
+
+extern struct pci_controller_ops pasemi_pci_controller_ops;
+
+#endif /* _PASEMI_PASEMI_H */
diff --git a/arch/powerpc/platforms/pasemi/pci.c b/arch/powerpc/platforms/pasemi/pci.c
new file mode 100644
index 0000000000..f27d314147
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/pci.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2006 PA Semi, Inc
+ *
+ * Authors: Kip Walker, PA Semi
+ * Olof Johansson, PA Semi
+ *
+ * Maintained by: Olof Johansson <olof@lixom.net>
+ *
+ * Based on arch/powerpc/platforms/maple/pci.c
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/of_address.h>
+#include <linux/pci.h>
+
+#include <asm/pci-bridge.h>
+#include <asm/isa-bridge.h>
+#include <asm/machdep.h>
+
+#include <asm/ppc-pci.h>
+
+#include "pasemi.h"
+
+#define PA_PXP_CFA(bus, devfn, off) (((bus) << 20) | ((devfn) << 12) | (off))
+
+static inline int pa_pxp_offset_valid(u8 bus, u8 devfn, int offset)
+{
+ /* Device 0 Function 0 is special: It's config space spans function 1 as
+ * well, so allow larger offset. It's really a two-function device but the
+ * second function does not probe.
+ */
+ if (bus == 0 && devfn == 0)
+ return offset < 8192;
+ else
+ return offset < 4096;
+}
+
+static void volatile __iomem *pa_pxp_cfg_addr(struct pci_controller *hose,
+ u8 bus, u8 devfn, int offset)
+{
+ return hose->cfg_data + PA_PXP_CFA(bus, devfn, offset);
+}
+
+static inline int is_root_port(int busno, int devfn)
+{
+ return ((busno == 0) && (PCI_FUNC(devfn) < 4) &&
+ ((PCI_SLOT(devfn) == 16) || (PCI_SLOT(devfn) == 17)));
+}
+
+static inline int is_5945_reg(int reg)
+{
+ return (((reg >= 0x18) && (reg < 0x34)) ||
+ ((reg >= 0x158) && (reg < 0x178)));
+}
+
+static int workaround_5945(struct pci_bus *bus, unsigned int devfn,
+ int offset, int len, u32 *val)
+{
+ struct pci_controller *hose;
+ void volatile __iomem *addr, *dummy;
+ int byte;
+ u32 tmp;
+
+ if (!is_root_port(bus->number, devfn) || !is_5945_reg(offset))
+ return 0;
+
+ hose = pci_bus_to_host(bus);
+
+ addr = pa_pxp_cfg_addr(hose, bus->number, devfn, offset & ~0x3);
+ byte = offset & 0x3;
+
+ /* Workaround bug 5945: write 0 to a dummy register before reading,
+ * and write back what we read. We must read/write the full 32-bit
+ * contents so we need to shift and mask by hand.
+ */
+ dummy = pa_pxp_cfg_addr(hose, bus->number, devfn, 0x10);
+ out_le32(dummy, 0);
+ tmp = in_le32(addr);
+ out_le32(addr, tmp);
+
+ switch (len) {
+ case 1:
+ *val = (tmp >> (8*byte)) & 0xff;
+ break;
+ case 2:
+ if (byte == 0)
+ *val = tmp & 0xffff;
+ else
+ *val = (tmp >> 16) & 0xffff;
+ break;
+ default:
+ *val = tmp;
+ break;
+ }
+
+ return 1;
+}
+
+#ifdef CONFIG_PPC_PASEMI_NEMO
+#define PXP_ERR_CFG_REG 0x4
+#define PXP_IGNORE_PCIE_ERRORS 0x800
+#define SB600_BUS 5
+
+static void sb600_set_flag(int bus)
+{
+ static void __iomem *iob_mapbase = NULL;
+ struct resource res;
+ struct device_node *dn;
+ int err;
+
+ if (iob_mapbase == NULL) {
+ dn = of_find_compatible_node(NULL, "isa", "pasemi,1682m-iob");
+ if (!dn) {
+ pr_crit("NEMO SB600 missing iob node\n");
+ return;
+ }
+
+ err = of_address_to_resource(dn, 0, &res);
+ of_node_put(dn);
+
+ if (err) {
+ pr_crit("NEMO SB600 missing resource\n");
+ return;
+ }
+
+ pr_info("NEMO SB600 IOB base %08llx\n",res.start);
+
+ iob_mapbase = ioremap(res.start + 0x100, 0x94);
+ }
+
+ if (iob_mapbase != NULL) {
+ if (bus == SB600_BUS) {
+ /*
+ * This is the SB600's bus, tell the PCI-e root port
+ * to allow non-zero devices to enumerate.
+ */
+ out_le32(iob_mapbase + PXP_ERR_CFG_REG, in_le32(iob_mapbase + PXP_ERR_CFG_REG) | PXP_IGNORE_PCIE_ERRORS);
+ } else {
+ /*
+ * Only scan device 0 on other busses
+ */
+ out_le32(iob_mapbase + PXP_ERR_CFG_REG, in_le32(iob_mapbase + PXP_ERR_CFG_REG) & ~PXP_IGNORE_PCIE_ERRORS);
+ }
+ }
+}
+
+#else
+
+static void sb600_set_flag(int bus)
+{
+}
+#endif
+
+static int pa_pxp_read_config(struct pci_bus *bus, unsigned int devfn,
+ int offset, int len, u32 *val)
+{
+ struct pci_controller *hose;
+ void volatile __iomem *addr;
+
+ hose = pci_bus_to_host(bus);
+ if (!hose)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ if (!pa_pxp_offset_valid(bus->number, devfn, offset))
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ if (workaround_5945(bus, devfn, offset, len, val))
+ return PCIBIOS_SUCCESSFUL;
+
+ addr = pa_pxp_cfg_addr(hose, bus->number, devfn, offset);
+
+ sb600_set_flag(bus->number);
+
+ /*
+ * Note: the caller has already checked that offset is
+ * suitably aligned and that len is 1, 2 or 4.
+ */
+ switch (len) {
+ case 1:
+ *val = in_8(addr);
+ break;
+ case 2:
+ *val = in_le16(addr);
+ break;
+ default:
+ *val = in_le32(addr);
+ break;
+ }
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int pa_pxp_write_config(struct pci_bus *bus, unsigned int devfn,
+ int offset, int len, u32 val)
+{
+ struct pci_controller *hose;
+ void volatile __iomem *addr;
+
+ hose = pci_bus_to_host(bus);
+ if (!hose)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ if (!pa_pxp_offset_valid(bus->number, devfn, offset))
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ addr = pa_pxp_cfg_addr(hose, bus->number, devfn, offset);
+
+ sb600_set_flag(bus->number);
+
+ /*
+ * Note: the caller has already checked that offset is
+ * suitably aligned and that len is 1, 2 or 4.
+ */
+ switch (len) {
+ case 1:
+ out_8(addr, val);
+ break;
+ case 2:
+ out_le16(addr, val);
+ break;
+ default:
+ out_le32(addr, val);
+ break;
+ }
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static struct pci_ops pa_pxp_ops = {
+ .read = pa_pxp_read_config,
+ .write = pa_pxp_write_config,
+};
+
+static void __init setup_pa_pxp(struct pci_controller *hose)
+{
+ hose->ops = &pa_pxp_ops;
+ hose->cfg_data = ioremap(0xe0000000, 0x10000000);
+}
+
+static int __init pas_add_bridge(struct device_node *dev)
+{
+ struct pci_controller *hose;
+
+ pr_debug("Adding PCI host bridge %pOF\n", dev);
+
+ hose = pcibios_alloc_controller(dev);
+ if (!hose)
+ return -ENOMEM;
+
+ hose->first_busno = 0;
+ hose->last_busno = 0xff;
+ hose->controller_ops = pasemi_pci_controller_ops;
+
+ setup_pa_pxp(hose);
+
+ pr_info("Found PA-PXP PCI host bridge.\n");
+
+ /* Interpret the "ranges" property */
+ pci_process_bridge_OF_ranges(hose, dev, 1);
+
+ /*
+ * Scan for an isa bridge. This is needed to find the SB600 on the nemo
+ * and does nothing on machines without one.
+ */
+ isa_bridge_find_early(hose);
+
+ return 0;
+}
+
+void __init pas_pci_init(void)
+{
+ struct device_node *np;
+ int res;
+
+ pci_set_flags(PCI_SCAN_ALL_PCIE_DEVS);
+
+ np = of_find_compatible_node(of_root, NULL, "pasemi,rootbus");
+ if (np) {
+ res = pas_add_bridge(np);
+ of_node_put(np);
+ }
+}
+
+void __iomem *__init pasemi_pci_getcfgaddr(struct pci_dev *dev, int offset)
+{
+ struct pci_controller *hose;
+
+ hose = pci_bus_to_host(dev->bus);
+
+ return (void __iomem *)pa_pxp_cfg_addr(hose, dev->bus->number, dev->devfn, offset);
+}
+
+struct pci_controller_ops pasemi_pci_controller_ops;
diff --git a/arch/powerpc/platforms/pasemi/powersave.S b/arch/powerpc/platforms/pasemi/powersave.S
new file mode 100644
index 0000000000..d0215d5329
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/powersave.S
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2006-2007 PA Semi, Inc
+ *
+ * Maintained by: Olof Johansson <olof@lixom.net>
+ */
+
+#include <asm/processor.h>
+#include <asm/page.h>
+#include <asm/ppc_asm.h>
+#include <asm/cputable.h>
+#include <asm/cache.h>
+#include <asm/thread_info.h>
+#include <asm/asm-offsets.h>
+
+/* Power savings opcodes since not all binutils have them at this time */
+#define DOZE .long 0x4c000324
+#define NAP .long 0x4c000364
+#define SLEEP .long 0x4c0003a4
+#define RVW .long 0x4c0003e4
+
+/* Common sequence to do before going to any of the
+ * powersavings modes.
+ */
+
+#define PRE_SLEEP_SEQUENCE \
+ std r3,8(r1); \
+ ptesync ; \
+ ld r3,8(r1); \
+1: cmpd r3,r3; \
+ bne 1b
+
+_doze:
+ PRE_SLEEP_SEQUENCE
+ DOZE
+ b .
+
+
+_GLOBAL(idle_spin)
+ blr
+
+_GLOBAL(idle_doze)
+ LOAD_REG_ADDR(r3, _doze)
+ b sleep_common
+
+/* Add more modes here later */
+
+sleep_common:
+ mflr r0
+ std r0, 16(r1)
+ stdu r1,-64(r1)
+#ifdef CONFIG_PPC_PASEMI_CPUFREQ
+ std r3, 48(r1)
+
+ /* Only do power savings when in astate 0 */
+ bl check_astate
+ cmpwi r3,0
+ bne 1f
+
+ ld r3, 48(r1)
+#endif
+ LOAD_REG_IMMEDIATE(r6,MSR_DR|MSR_IR|MSR_ME|MSR_EE)
+ mfmsr r4
+ andc r5,r4,r6
+ mtmsrd r5,0
+
+ mtctr r3
+ bctrl
+
+ mtmsrd r4,0
+
+1: addi r1,r1,64
+ ld r0,16(r1)
+ mtlr r0
+ blr
+
diff --git a/arch/powerpc/platforms/pasemi/setup.c b/arch/powerpc/platforms/pasemi/setup.c
new file mode 100644
index 0000000000..ef985ba2bf
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/setup.c
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2006-2007 PA Semi, Inc
+ *
+ * Authors: Kip Walker, PA Semi
+ * Olof Johansson, PA Semi
+ *
+ * Maintained by: Olof Johansson <olof@lixom.net>
+ *
+ * Based on arch/powerpc/platforms/maple/setup.c
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/console.h>
+#include <linux/export.h>
+#include <linux/pci.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/gfp.h>
+#include <linux/irqdomain.h>
+
+#include <asm/iommu.h>
+#include <asm/machdep.h>
+#include <asm/i8259.h>
+#include <asm/mpic.h>
+#include <asm/smp.h>
+#include <asm/time.h>
+#include <asm/mmu.h>
+#include <asm/debug.h>
+
+#include <pcmcia/ss.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+#include "pasemi.h"
+
+/* SDC reset register, must be pre-mapped at reset time */
+static void __iomem *reset_reg;
+
+/* Various error status registers, must be pre-mapped at MCE time */
+
+#define MAX_MCE_REGS 32
+struct mce_regs {
+ char *name;
+ void __iomem *addr;
+};
+
+static struct mce_regs mce_regs[MAX_MCE_REGS];
+static int num_mce_regs;
+static int nmi_virq = 0;
+
+
+static void __noreturn pas_restart(char *cmd)
+{
+ /* Need to put others cpu in hold loop so they're not sleeping */
+ smp_send_stop();
+ udelay(10000);
+ printk("Restarting...\n");
+ while (1)
+ out_le32(reset_reg, 0x6000000);
+}
+
+#ifdef CONFIG_PPC_PASEMI_NEMO
+void pas_shutdown(void)
+{
+ /* Set the PLD bit that makes the SB600 think the power button is being pressed */
+ void __iomem *pld_map = ioremap(0xf5000000,4096);
+ while (1)
+ out_8(pld_map+7,0x01);
+}
+
+/* RTC platform device structure as is not in device tree */
+static struct resource rtc_resource[] = {{
+ .name = "rtc",
+ .start = 0x70,
+ .end = 0x71,
+ .flags = IORESOURCE_IO,
+}, {
+ .name = "rtc",
+ .start = 8,
+ .end = 8,
+ .flags = IORESOURCE_IRQ,
+}};
+
+static inline void nemo_init_rtc(void)
+{
+ platform_device_register_simple("rtc_cmos", -1, rtc_resource, 2);
+}
+
+#else
+
+static inline void nemo_init_rtc(void)
+{
+}
+#endif
+
+#ifdef CONFIG_SMP
+static arch_spinlock_t timebase_lock;
+static unsigned long timebase;
+
+static void pas_give_timebase(void)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ hard_irq_disable();
+ arch_spin_lock(&timebase_lock);
+ mtspr(SPRN_TBCTL, TBCTL_FREEZE);
+ isync();
+ timebase = get_tb();
+ arch_spin_unlock(&timebase_lock);
+
+ while (timebase)
+ barrier();
+ mtspr(SPRN_TBCTL, TBCTL_RESTART);
+ local_irq_restore(flags);
+}
+
+static void pas_take_timebase(void)
+{
+ while (!timebase)
+ smp_rmb();
+
+ arch_spin_lock(&timebase_lock);
+ set_tb(timebase >> 32, timebase & 0xffffffff);
+ timebase = 0;
+ arch_spin_unlock(&timebase_lock);
+}
+
+static struct smp_ops_t pas_smp_ops = {
+ .probe = smp_mpic_probe,
+ .message_pass = smp_mpic_message_pass,
+ .kick_cpu = smp_generic_kick_cpu,
+ .setup_cpu = smp_mpic_setup_cpu,
+ .give_timebase = pas_give_timebase,
+ .take_timebase = pas_take_timebase,
+};
+#endif /* CONFIG_SMP */
+
+static void __init pas_setup_arch(void)
+{
+#ifdef CONFIG_SMP
+ /* Setup SMP callback */
+ smp_ops = &pas_smp_ops;
+#endif
+
+ /* Remap SDC register for doing reset */
+ /* XXXOJN This should maybe come out of the device tree */
+ reset_reg = ioremap(0xfc101100, 4);
+}
+
+static int __init pas_setup_mce_regs(void)
+{
+ struct pci_dev *dev;
+ int reg;
+
+ /* Remap various SoC status registers for use by the MCE handler */
+
+ reg = 0;
+
+ dev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa00a, NULL);
+ while (dev && reg < MAX_MCE_REGS) {
+ mce_regs[reg].name = kasprintf(GFP_KERNEL,
+ "mc%d_mcdebug_errsta", reg);
+ mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0x730);
+ dev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa00a, dev);
+ reg++;
+ }
+
+ dev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa001, NULL);
+ if (dev && reg+4 < MAX_MCE_REGS) {
+ mce_regs[reg].name = "iobdbg_IntStatus1";
+ mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0x438);
+ reg++;
+ mce_regs[reg].name = "iobdbg_IOCTbusIntDbgReg";
+ mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0x454);
+ reg++;
+ mce_regs[reg].name = "iobiom_IntStatus";
+ mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0xc10);
+ reg++;
+ mce_regs[reg].name = "iobiom_IntDbgReg";
+ mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0xc1c);
+ reg++;
+ }
+
+ dev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa009, NULL);
+ if (dev && reg+2 < MAX_MCE_REGS) {
+ mce_regs[reg].name = "l2csts_IntStatus";
+ mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0x200);
+ reg++;
+ mce_regs[reg].name = "l2csts_Cnt";
+ mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0x214);
+ reg++;
+ }
+
+ num_mce_regs = reg;
+
+ return 0;
+}
+machine_device_initcall(pasemi, pas_setup_mce_regs);
+
+#ifdef CONFIG_PPC_PASEMI_NEMO
+static void sb600_8259_cascade(struct irq_desc *desc)
+{
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int cascade_irq = i8259_irq();
+
+ if (cascade_irq)
+ generic_handle_irq(cascade_irq);
+
+ chip->irq_eoi(&desc->irq_data);
+}
+
+static void __init nemo_init_IRQ(struct mpic *mpic)
+{
+ struct device_node *np;
+ int gpio_virq;
+ /* Connect the SB600's legacy i8259 controller */
+ np = of_find_node_by_path("/pxp@0,e0000000");
+ i8259_init(np, 0);
+ of_node_put(np);
+
+ gpio_virq = irq_create_mapping(NULL, 3);
+ irq_set_irq_type(gpio_virq, IRQ_TYPE_LEVEL_HIGH);
+ irq_set_chained_handler(gpio_virq, sb600_8259_cascade);
+ mpic_unmask_irq(irq_get_irq_data(gpio_virq));
+
+ irq_set_default_host(mpic->irqhost);
+}
+
+#else
+
+static inline void nemo_init_IRQ(struct mpic *mpic)
+{
+}
+#endif
+
+static __init void pas_init_IRQ(void)
+{
+ struct device_node *np;
+ struct device_node *root, *mpic_node;
+ unsigned long openpic_addr;
+ const unsigned int *opprop;
+ int naddr, opplen;
+ int mpic_flags;
+ const unsigned int *nmiprop;
+ struct mpic *mpic;
+
+ mpic_node = NULL;
+
+ for_each_node_by_type(np, "interrupt-controller")
+ if (of_device_is_compatible(np, "open-pic")) {
+ mpic_node = np;
+ break;
+ }
+ if (!mpic_node)
+ for_each_node_by_type(np, "open-pic") {
+ mpic_node = np;
+ break;
+ }
+ if (!mpic_node) {
+ pr_err("Failed to locate the MPIC interrupt controller\n");
+ return;
+ }
+
+ /* Find address list in /platform-open-pic */
+ root = of_find_node_by_path("/");
+ naddr = of_n_addr_cells(root);
+ opprop = of_get_property(root, "platform-open-pic", &opplen);
+ if (!opprop) {
+ pr_err("No platform-open-pic property.\n");
+ of_node_put(root);
+ return;
+ }
+ openpic_addr = of_read_number(opprop, naddr);
+ pr_debug("OpenPIC addr: %lx\n", openpic_addr);
+
+ mpic_flags = MPIC_LARGE_VECTORS | MPIC_NO_BIAS | MPIC_NO_RESET;
+
+ nmiprop = of_get_property(mpic_node, "nmi-source", NULL);
+ if (nmiprop)
+ mpic_flags |= MPIC_ENABLE_MCK;
+
+ mpic = mpic_alloc(mpic_node, openpic_addr,
+ mpic_flags, 0, 0, "PASEMI-OPIC");
+ BUG_ON(!mpic);
+
+ mpic_assign_isu(mpic, 0, mpic->paddr + 0x10000);
+ mpic_init(mpic);
+ /* The NMI/MCK source needs to be prio 15 */
+ if (nmiprop) {
+ nmi_virq = irq_create_mapping(NULL, *nmiprop);
+ mpic_irq_set_priority(nmi_virq, 15);
+ irq_set_irq_type(nmi_virq, IRQ_TYPE_EDGE_RISING);
+ mpic_unmask_irq(irq_get_irq_data(nmi_virq));
+ }
+
+ nemo_init_IRQ(mpic);
+
+ of_node_put(mpic_node);
+ of_node_put(root);
+}
+
+static void __init pas_progress(char *s, unsigned short hex)
+{
+ printk("[%04x] : %s\n", hex, s ? s : "");
+}
+
+
+static int pas_machine_check_handler(struct pt_regs *regs)
+{
+ int cpu = smp_processor_id();
+ unsigned long srr0, srr1, dsisr;
+ int dump_slb = 0;
+ int i;
+
+ srr0 = regs->nip;
+ srr1 = regs->msr;
+
+ if (nmi_virq && mpic_get_mcirq() == nmi_virq) {
+ pr_err("NMI delivered\n");
+ debugger(regs);
+ mpic_end_irq(irq_get_irq_data(nmi_virq));
+ goto out;
+ }
+
+ dsisr = mfspr(SPRN_DSISR);
+ pr_err("Machine Check on CPU %d\n", cpu);
+ pr_err("SRR0 0x%016lx SRR1 0x%016lx\n", srr0, srr1);
+ pr_err("DSISR 0x%016lx DAR 0x%016lx\n", dsisr, regs->dar);
+ pr_err("BER 0x%016lx MER 0x%016lx\n", mfspr(SPRN_PA6T_BER),
+ mfspr(SPRN_PA6T_MER));
+ pr_err("IER 0x%016lx DER 0x%016lx\n", mfspr(SPRN_PA6T_IER),
+ mfspr(SPRN_PA6T_DER));
+ pr_err("Cause:\n");
+
+ if (srr1 & 0x200000)
+ pr_err("Signalled by SDC\n");
+
+ if (srr1 & 0x100000) {
+ pr_err("Load/Store detected error:\n");
+ if (dsisr & 0x8000)
+ pr_err("D-cache ECC double-bit error or bus error\n");
+ if (dsisr & 0x4000)
+ pr_err("LSU snoop response error\n");
+ if (dsisr & 0x2000) {
+ pr_err("MMU SLB multi-hit or invalid B field\n");
+ dump_slb = 1;
+ }
+ if (dsisr & 0x1000)
+ pr_err("Recoverable Duptags\n");
+ if (dsisr & 0x800)
+ pr_err("Recoverable D-cache parity error count overflow\n");
+ if (dsisr & 0x400)
+ pr_err("TLB parity error count overflow\n");
+ }
+
+ if (srr1 & 0x80000)
+ pr_err("Bus Error\n");
+
+ if (srr1 & 0x40000) {
+ pr_err("I-side SLB multiple hit\n");
+ dump_slb = 1;
+ }
+
+ if (srr1 & 0x20000)
+ pr_err("I-cache parity error hit\n");
+
+ if (num_mce_regs == 0)
+ pr_err("No MCE registers mapped yet, can't dump\n");
+ else
+ pr_err("SoC debug registers:\n");
+
+ for (i = 0; i < num_mce_regs; i++)
+ pr_err("%s: 0x%08x\n", mce_regs[i].name,
+ in_le32(mce_regs[i].addr));
+
+ if (dump_slb) {
+ unsigned long e, v;
+ int i;
+
+ pr_err("slb contents:\n");
+ for (i = 0; i < mmu_slb_size; i++) {
+ asm volatile("slbmfee %0,%1" : "=r" (e) : "r" (i));
+ asm volatile("slbmfev %0,%1" : "=r" (v) : "r" (i));
+ pr_err("%02d %016lx %016lx\n", i, e, v);
+ }
+ }
+
+out:
+ /* SRR1[62] is from MSR[62] if recoverable, so pass that back */
+ return !!(srr1 & 0x2);
+}
+
+static const struct of_device_id pasemi_bus_ids[] = {
+ /* Unfortunately needed for legacy firmwares */
+ { .type = "localbus", },
+ { .type = "sdc", },
+ /* These are the proper entries, which newer firmware uses */
+ { .compatible = "pasemi,localbus", },
+ { .compatible = "pasemi,sdc", },
+ {},
+};
+
+static int __init pasemi_publish_devices(void)
+{
+ /* Publish OF platform devices for SDC and other non-PCI devices */
+ of_platform_bus_probe(NULL, pasemi_bus_ids, NULL);
+
+ nemo_init_rtc();
+
+ return 0;
+}
+machine_device_initcall(pasemi, pasemi_publish_devices);
+
+
+/*
+ * Called very early, MMU is off, device-tree isn't unflattened
+ */
+static int __init pas_probe(void)
+{
+ if (!of_machine_is_compatible("PA6T-1682M") &&
+ !of_machine_is_compatible("pasemi,pwrficient"))
+ return 0;
+
+#ifdef CONFIG_PPC_PASEMI_NEMO
+ /*
+ * Check for the Nemo motherboard here, if we are running on one
+ * change the machine definition to fit
+ */
+ if (of_machine_is_compatible("pasemi,nemo")) {
+ pm_power_off = pas_shutdown;
+ ppc_md.name = "A-EON Amigaone X1000";
+ }
+#endif
+
+ iommu_init_early_pasemi();
+
+ return 1;
+}
+
+define_machine(pasemi) {
+ .name = "PA Semi PWRficient",
+ .probe = pas_probe,
+ .setup_arch = pas_setup_arch,
+ .discover_phbs = pas_pci_init,
+ .init_IRQ = pas_init_IRQ,
+ .get_irq = mpic_get_irq,
+ .restart = pas_restart,
+ .get_boot_time = pas_get_boot_time,
+ .progress = pas_progress,
+ .machine_check_exception = pas_machine_check_handler,
+};
diff --git a/arch/powerpc/platforms/pasemi/time.c b/arch/powerpc/platforms/pasemi/time.c
new file mode 100644
index 0000000000..70ac6db027
--- /dev/null
+++ b/arch/powerpc/platforms/pasemi/time.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2006 PA Semi, Inc
+ *
+ * Maintained by: Olof Johansson <olof@lixom.net>
+ */
+
+#include <linux/time.h>
+
+#include <asm/time.h>
+
+#include "pasemi.h"
+
+time64_t __init pas_get_boot_time(void)
+{
+ /* Let's just return a fake date right now */
+ return mktime64(2006, 1, 1, 12, 0, 0);
+}