summaryrefslogtreecommitdiff
path: root/drivers/soc/apple/dockchannel.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soc/apple/dockchannel.c')
-rw-r--r--drivers/soc/apple/dockchannel.c407
1 files changed, 407 insertions, 0 deletions
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");