mirror of
https://github.com/Heleguo/lede.git
synced 2025-12-22 13:51:16 +00:00
502 lines
14 KiB
Diff
502 lines
14 KiB
Diff
From ffeb65333caa066616ee70e0257035fe99cf4205 Mon Sep 17 00:00:00 2001
|
|
From: Hector Martin <marcan@marcan.st>
|
|
Date: Fri, 8 Jul 2022 02:09:24 +0900
|
|
Subject: [PATCH 129/171] soc: apple: Add DockChannel driver
|
|
|
|
DockChannel is a simple FIFO interface used to communicate between SoC
|
|
blocks. Add a driver that represents the shared interrupt controller for
|
|
the DockChannel block, and then exposes probe and data transfer
|
|
functions that child device drivers can use to instantiate individual
|
|
FIFOs.
|
|
|
|
Signed-off-by: Hector Martin <marcan@marcan.st>
|
|
---
|
|
drivers/soc/apple/Kconfig | 10 +
|
|
drivers/soc/apple/Makefile | 3 +
|
|
drivers/soc/apple/dockchannel.c | 407 ++++++++++++++++++++++++++
|
|
include/linux/soc/apple/dockchannel.h | 26 ++
|
|
4 files changed, 446 insertions(+)
|
|
create mode 100644 drivers/soc/apple/dockchannel.c
|
|
create mode 100644 include/linux/soc/apple/dockchannel.h
|
|
|
|
diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig
|
|
index a1596fefacff..9e93df0ac825 100644
|
|
--- a/drivers/soc/apple/Kconfig
|
|
+++ b/drivers/soc/apple/Kconfig
|
|
@@ -41,6 +41,16 @@ config APPLE_SART
|
|
|
|
Say 'y' here if you have an Apple SoC.
|
|
|
|
+config APPLE_DOCKCHANNEL
|
|
+ tristate "Apple DockChannel FIFO"
|
|
+ depends on ARCH_APPLE || COMPILE_TEST
|
|
+ default ARCH_APPLE
|
|
+ help
|
|
+ DockChannel is a simple FIFO used on Apple SoCs for debug and inter-processor
|
|
+ communications.
|
|
+
|
|
+ Say 'y' here if you have an Apple SoC.
|
|
+
|
|
endmenu
|
|
|
|
endif
|
|
diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile
|
|
index e293770cf66d..aa4203e02462 100644
|
|
--- a/drivers/soc/apple/Makefile
|
|
+++ b/drivers/soc/apple/Makefile
|
|
@@ -6,3 +6,6 @@ apple-rtkit-y = rtkit.o rtkit-crashlog.o
|
|
|
|
obj-$(CONFIG_APPLE_SART) += apple-sart.o
|
|
apple-sart-y = sart.o
|
|
+
|
|
+obj-$(CONFIG_APPLE_DOCKCHANNEL) += apple-dockchannel.o
|
|
+apple-dockchannel-y = dockchannel.o
|
|
diff --git a/drivers/soc/apple/dockchannel.c b/drivers/soc/apple/dockchannel.c
|
|
new file mode 100644
|
|
index 000000000000..c21ef7545e17
|
|
--- /dev/null
|
|
+++ b/drivers/soc/apple/dockchannel.c
|
|
@@ -0,0 +1,407 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
|
|
+/*
|
|
+ * Apple DockChannel FIFO driver
|
|
+ * Copyright The Asahi Linux Contributors
|
|
+ */
|
|
+
|
|
+#include <asm/unaligned.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/irqchip/chained_irq.h>
|
|
+#include <linux/irqdomain.h>
|
|
+#include <linux/soc/apple/dockchannel.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_platform.h>
|
|
+
|
|
+#define DOCKCHANNEL_MAX_IRQ 32
|
|
+
|
|
+#define DOCKCHANNEL_TX_TIMEOUT_MS 1000
|
|
+#define DOCKCHANNEL_RX_TIMEOUT_MS 1000
|
|
+
|
|
+#define IRQ_MASK 0x0
|
|
+#define IRQ_FLAG 0x4
|
|
+
|
|
+#define IRQ_TX BIT(0)
|
|
+#define IRQ_RX BIT(1)
|
|
+
|
|
+#define CONFIG_TX_THRESH 0x0
|
|
+#define CONFIG_RX_THRESH 0x4
|
|
+
|
|
+#define DATA_TX8 0x4
|
|
+#define DATA_TX16 0x8
|
|
+#define DATA_TX24 0xc
|
|
+#define DATA_TX32 0x10
|
|
+#define DATA_TX_FREE 0x14
|
|
+#define DATA_RX8 0x1c
|
|
+#define DATA_RX16 0x20
|
|
+#define DATA_RX24 0x24
|
|
+#define DATA_RX32 0x28
|
|
+#define DATA_RX_COUNT 0x2c
|
|
+
|
|
+struct dockchannel {
|
|
+ struct device *dev;
|
|
+ int tx_irq;
|
|
+ int rx_irq;
|
|
+
|
|
+ void __iomem *config_base;
|
|
+ void __iomem *data_base;
|
|
+
|
|
+ u32 fifo_size;
|
|
+ bool awaiting;
|
|
+ struct completion tx_comp;
|
|
+ struct completion rx_comp;
|
|
+
|
|
+ void *cookie;
|
|
+ void (*data_available)(void *cookie, size_t avail);
|
|
+};
|
|
+
|
|
+struct dockchannel_common {
|
|
+ struct device *dev;
|
|
+ struct irq_domain *domain;
|
|
+ int irq;
|
|
+
|
|
+ void __iomem *irq_base;
|
|
+};
|
|
+
|
|
+/* Dockchannel FIFO functions */
|
|
+
|
|
+static irqreturn_t dockchannel_tx_irq(int irq, void *data)
|
|
+{
|
|
+ struct dockchannel *dockchannel = data;
|
|
+
|
|
+ disable_irq_nosync(irq);
|
|
+ complete(&dockchannel->tx_comp);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static irqreturn_t dockchannel_rx_irq(int irq, void *data)
|
|
+{
|
|
+ struct dockchannel *dockchannel = data;
|
|
+
|
|
+ disable_irq_nosync(irq);
|
|
+
|
|
+ if (dockchannel->awaiting) {
|
|
+ return IRQ_WAKE_THREAD;
|
|
+ } else {
|
|
+ complete(&dockchannel->rx_comp);
|
|
+ return IRQ_HANDLED;
|
|
+ }
|
|
+}
|
|
+
|
|
+static irqreturn_t dockchannel_rx_irq_thread(int irq, void *data)
|
|
+{
|
|
+ struct dockchannel *dockchannel = data;
|
|
+ size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT);
|
|
+
|
|
+ dockchannel->awaiting = false;
|
|
+ dockchannel->data_available(dockchannel->cookie, avail);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count)
|
|
+{
|
|
+ size_t left = count;
|
|
+ const u8 *p = buf;
|
|
+
|
|
+ while (left > 0) {
|
|
+ size_t avail = readl_relaxed(dockchannel->data_base + DATA_TX_FREE);
|
|
+ size_t block = min(left, avail);
|
|
+
|
|
+ if (avail == 0) {
|
|
+ size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left);
|
|
+
|
|
+ writel_relaxed(threshold, dockchannel->config_base + CONFIG_TX_THRESH);
|
|
+ reinit_completion(&dockchannel->tx_comp);
|
|
+ enable_irq(dockchannel->tx_irq);
|
|
+
|
|
+ if (!wait_for_completion_timeout(&dockchannel->tx_comp,
|
|
+ msecs_to_jiffies(DOCKCHANNEL_TX_TIMEOUT_MS))) {
|
|
+ disable_irq(dockchannel->tx_irq);
|
|
+ return -ETIMEDOUT;
|
|
+ }
|
|
+
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ while (block >= 4) {
|
|
+ writel_relaxed(get_unaligned_le32(p), dockchannel->data_base + DATA_TX32);
|
|
+ p += 4;
|
|
+ left -= 4;
|
|
+ block -= 4;
|
|
+ }
|
|
+ while (block > 0) {
|
|
+ writeb_relaxed(*p++, dockchannel->data_base + DATA_TX8);
|
|
+ left--;
|
|
+ block--;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+EXPORT_SYMBOL(dockchannel_send);
|
|
+
|
|
+int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count)
|
|
+{
|
|
+ size_t left = count;
|
|
+ u8 *p = buf;
|
|
+
|
|
+ while (left > 0) {
|
|
+ size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT);
|
|
+ size_t block = min(left, avail);
|
|
+
|
|
+ if (avail == 0) {
|
|
+ size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left);
|
|
+
|
|
+ writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH);
|
|
+ reinit_completion(&dockchannel->rx_comp);
|
|
+ enable_irq(dockchannel->rx_irq);
|
|
+
|
|
+ if (!wait_for_completion_timeout(&dockchannel->rx_comp,
|
|
+ msecs_to_jiffies(DOCKCHANNEL_RX_TIMEOUT_MS))) {
|
|
+ disable_irq(dockchannel->rx_irq);
|
|
+ return -ETIMEDOUT;
|
|
+ }
|
|
+
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ while (block >= 4) {
|
|
+ put_unaligned_le32(readl_relaxed(dockchannel->data_base + DATA_RX32), p);
|
|
+ p += 4;
|
|
+ left -= 4;
|
|
+ block -= 4;
|
|
+ }
|
|
+ while (block > 0) {
|
|
+ *p++ = readl_relaxed(dockchannel->data_base + DATA_RX8) >> 8;
|
|
+ left--;
|
|
+ block--;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+EXPORT_SYMBOL(dockchannel_recv);
|
|
+
|
|
+int dockchannel_await(struct dockchannel *dockchannel,
|
|
+ void (*callback)(void *cookie, size_t avail),
|
|
+ void *cookie, size_t count)
|
|
+{
|
|
+ size_t threshold = min((size_t)dockchannel->fifo_size, count);
|
|
+
|
|
+ if (!count) {
|
|
+ dockchannel->awaiting = false;
|
|
+ disable_irq(dockchannel->rx_irq);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ dockchannel->data_available = callback;
|
|
+ dockchannel->cookie = cookie;
|
|
+ dockchannel->awaiting = true;
|
|
+ writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH);
|
|
+ enable_irq(dockchannel->rx_irq);
|
|
+
|
|
+ return threshold;
|
|
+}
|
|
+EXPORT_SYMBOL(dockchannel_await);
|
|
+
|
|
+struct dockchannel *dockchannel_init(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct dockchannel *dockchannel;
|
|
+ int ret;
|
|
+
|
|
+ dockchannel = devm_kzalloc(dev, sizeof(*dockchannel), GFP_KERNEL);
|
|
+ if (!dockchannel)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ dockchannel->dev = dev;
|
|
+ dockchannel->config_base = devm_platform_ioremap_resource_byname(pdev, "config");
|
|
+ if (IS_ERR(dockchannel->config_base))
|
|
+ return (void *)dockchannel->config_base;
|
|
+
|
|
+ dockchannel->data_base = devm_platform_ioremap_resource_byname(pdev, "data");
|
|
+ if (IS_ERR(dockchannel->data_base))
|
|
+ return (void *)dockchannel->data_base;
|
|
+
|
|
+ ret = of_property_read_u32(dev->of_node, "apple,fifo-size", &dockchannel->fifo_size);
|
|
+ if (ret)
|
|
+ return ERR_PTR(dev_err_probe(dev, ret, "Missing apple,fifo-size property"));
|
|
+
|
|
+ init_completion(&dockchannel->tx_comp);
|
|
+ init_completion(&dockchannel->rx_comp);
|
|
+
|
|
+ dockchannel->tx_irq = platform_get_irq_byname(pdev, "tx");
|
|
+ if (dockchannel->tx_irq <= 0) {
|
|
+ return ERR_PTR(dev_err_probe(dev, dockchannel->tx_irq,
|
|
+ "Failed to get TX IRQ"));
|
|
+ }
|
|
+
|
|
+ dockchannel->rx_irq = platform_get_irq_byname(pdev, "rx");
|
|
+ if (dockchannel->rx_irq <= 0) {
|
|
+ return ERR_PTR(dev_err_probe(dev, dockchannel->rx_irq,
|
|
+ "Failed to get RX IRQ"));
|
|
+ }
|
|
+
|
|
+ ret = devm_request_irq(dev, dockchannel->tx_irq, dockchannel_tx_irq, IRQF_NO_AUTOEN,
|
|
+ "apple-dockchannel-tx", dockchannel);
|
|
+ if (ret)
|
|
+ return ERR_PTR(dev_err_probe(dev, ret, "Failed to request TX IRQ"));
|
|
+
|
|
+ ret = devm_request_threaded_irq(dev, dockchannel->rx_irq, dockchannel_rx_irq,
|
|
+ dockchannel_rx_irq_thread, IRQF_NO_AUTOEN,
|
|
+ "apple-dockchannel-rx", dockchannel);
|
|
+ if (ret)
|
|
+ return ERR_PTR(dev_err_probe(dev, ret, "Failed to request RX IRQ"));
|
|
+
|
|
+ return dockchannel;
|
|
+}
|
|
+EXPORT_SYMBOL(dockchannel_init);
|
|
+
|
|
+
|
|
+/* Dockchannel IRQchip */
|
|
+
|
|
+static void dockchannel_irq(struct irq_desc *desc)
|
|
+{
|
|
+ unsigned int irq = irq_desc_get_irq(desc);
|
|
+ struct irq_chip *chip = irq_desc_get_chip(desc);
|
|
+ struct dockchannel_common *dcc = irq_get_handler_data(irq);
|
|
+ unsigned long flags = readl_relaxed(dcc->irq_base + IRQ_FLAG);
|
|
+ int bit;
|
|
+
|
|
+ chained_irq_enter(chip, desc);
|
|
+
|
|
+ for_each_set_bit(bit, &flags, DOCKCHANNEL_MAX_IRQ)
|
|
+ generic_handle_domain_irq(dcc->domain, bit);
|
|
+
|
|
+ chained_irq_exit(chip, desc);
|
|
+}
|
|
+
|
|
+static void dockchannel_irq_ack(struct irq_data *data)
|
|
+{
|
|
+ struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data);
|
|
+ unsigned int hwirq = data->hwirq;
|
|
+
|
|
+ writel_relaxed(BIT(hwirq), dcc->irq_base + IRQ_FLAG);
|
|
+}
|
|
+
|
|
+static void dockchannel_irq_mask(struct irq_data *data)
|
|
+{
|
|
+ struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data);
|
|
+ unsigned int hwirq = data->hwirq;
|
|
+ u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK);
|
|
+
|
|
+ writel_relaxed(val & ~BIT(hwirq), dcc->irq_base + IRQ_MASK);
|
|
+}
|
|
+
|
|
+static void dockchannel_irq_unmask(struct irq_data *data)
|
|
+{
|
|
+ struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data);
|
|
+ unsigned int hwirq = data->hwirq;
|
|
+ u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK);
|
|
+
|
|
+ writel_relaxed(val | BIT(hwirq), dcc->irq_base + IRQ_MASK);
|
|
+}
|
|
+
|
|
+static const struct irq_chip dockchannel_irqchip = {
|
|
+ .name = "dockchannel-irqc",
|
|
+ .irq_ack = dockchannel_irq_ack,
|
|
+ .irq_mask = dockchannel_irq_mask,
|
|
+ .irq_unmask = dockchannel_irq_unmask,
|
|
+};
|
|
+
|
|
+static int dockchannel_irq_domain_map(struct irq_domain *d, unsigned int virq,
|
|
+ irq_hw_number_t hw)
|
|
+{
|
|
+ irq_set_chip_data(virq, d->host_data);
|
|
+ irq_set_chip_and_handler(virq, &dockchannel_irqchip, handle_level_irq);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct irq_domain_ops dockchannel_irq_domain_ops = {
|
|
+ .xlate = irq_domain_xlate_twocell,
|
|
+ .map = dockchannel_irq_domain_map,
|
|
+};
|
|
+
|
|
+static int dockchannel_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct dockchannel_common *dcc;
|
|
+ struct device_node *child;
|
|
+
|
|
+ dcc = devm_kzalloc(dev, sizeof(*dcc), GFP_KERNEL);
|
|
+ if (!dcc)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ dcc->dev = dev;
|
|
+ platform_set_drvdata(pdev, dcc);
|
|
+
|
|
+ dcc->irq_base = devm_platform_ioremap_resource_byname(pdev, "irq");
|
|
+ if (IS_ERR(dcc->irq_base))
|
|
+ return PTR_ERR(dcc->irq_base);
|
|
+
|
|
+ writel_relaxed(0, dcc->irq_base + IRQ_MASK);
|
|
+ writel_relaxed(~0, dcc->irq_base + IRQ_FLAG);
|
|
+
|
|
+ dcc->domain = irq_domain_add_linear(dev->of_node, DOCKCHANNEL_MAX_IRQ,
|
|
+ &dockchannel_irq_domain_ops, dcc);
|
|
+ if (!dcc->domain)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ dcc->irq = platform_get_irq(pdev, 0);
|
|
+ if (dcc->irq <= 0)
|
|
+ return dev_err_probe(dev, dcc->irq, "Failed to get IRQ");
|
|
+
|
|
+ irq_set_handler_data(dcc->irq, dcc);
|
|
+ irq_set_chained_handler(dcc->irq, dockchannel_irq);
|
|
+
|
|
+ for_each_child_of_node(dev->of_node, child)
|
|
+ of_platform_device_create(child, NULL, dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dockchannel_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct dockchannel_common *dcc = platform_get_drvdata(pdev);
|
|
+ int hwirq;
|
|
+
|
|
+ device_for_each_child(&pdev->dev, NULL, of_platform_device_destroy);
|
|
+
|
|
+ irq_set_chained_handler_and_data(dcc->irq, NULL, NULL);
|
|
+
|
|
+ for (hwirq = 0; hwirq < DOCKCHANNEL_MAX_IRQ; hwirq++)
|
|
+ irq_dispose_mapping(irq_find_mapping(dcc->domain, hwirq));
|
|
+
|
|
+ irq_domain_remove(dcc->domain);
|
|
+
|
|
+ writel_relaxed(0, dcc->irq_base + IRQ_MASK);
|
|
+ writel_relaxed(~0, dcc->irq_base + IRQ_FLAG);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id dockchannel_of_match[] = {
|
|
+ { .compatible = "apple,dockchannel" },
|
|
+ {},
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, dockchannel_of_match);
|
|
+
|
|
+static struct platform_driver dockchannel_driver = {
|
|
+ .driver = {
|
|
+ .name = "dockchannel",
|
|
+ .of_match_table = dockchannel_of_match,
|
|
+ },
|
|
+ .probe = dockchannel_probe,
|
|
+ .remove = dockchannel_remove,
|
|
+};
|
|
+module_platform_driver(dockchannel_driver);
|
|
+
|
|
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
|
|
+MODULE_LICENSE("Dual MIT/GPL");
|
|
+MODULE_DESCRIPTION("Apple DockChannel driver");
|
|
diff --git a/include/linux/soc/apple/dockchannel.h b/include/linux/soc/apple/dockchannel.h
|
|
new file mode 100644
|
|
index 000000000000..0b7093935ddf
|
|
--- /dev/null
|
|
+++ b/include/linux/soc/apple/dockchannel.h
|
|
@@ -0,0 +1,26 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
|
|
+/*
|
|
+ * Apple Dockchannel devices
|
|
+ * Copyright (C) The Asahi Linux Contributors
|
|
+ */
|
|
+#ifndef _LINUX_APPLE_DOCKCHANNEL_H_
|
|
+#define _LINUX_APPLE_DOCKCHANNEL_H_
|
|
+
|
|
+#include <linux/device.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/of_platform.h>
|
|
+
|
|
+#if IS_ENABLED(CONFIG_APPLE_DOCKCHANNEL)
|
|
+
|
|
+struct dockchannel;
|
|
+
|
|
+struct dockchannel *dockchannel_init(struct platform_device *pdev);
|
|
+
|
|
+int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count);
|
|
+int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count);
|
|
+int dockchannel_await(struct dockchannel *dockchannel,
|
|
+ void (*callback)(void *cookie, size_t avail),
|
|
+ void *cookie, size_t count);
|
|
+
|
|
+#endif
|
|
+#endif
|
|
--
|
|
2.34.1
|
|
|