* [PATCH 1/3] drivers/iio/adc: Add XuanTie TH1520 ADC driver
From: wefu @ 2024-03-29 20:01 UTC (permalink / raw)
To: jszhang, guoren, conor, robh, krzysztof.kozlowski+dt,
paul.walmsley, palmer, aou, jic23, lars, andriy.shevchenko,
nuno.sa, marcelo.schmitt, bigunclemax, marius.cristea, fr0st61te,
okan.sahin, marcus.folkesson, schnelle, lee, mike.looijmans
Cc: linux-riscv, devicetree, linux-kernel, linux-iio, Wei Fu
In-Reply-To: <20240329200241.4122000-1-wefu@redhat.com>
From: Wei Fu <wefu@redhat.com>
Signed-off-by: Wei Fu <wefu@redhat.com>
---
drivers/iio/adc/Kconfig | 13 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/xuantie-th1520-adc.c | 572 +++++++++++++++++++++++++++
drivers/iio/adc/xuantie-th1520-adc.h | 193 +++++++++
4 files changed, 779 insertions(+)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 0d9282fa67f5..9e37ba2a877a 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1515,4 +1515,17 @@ config XILINX_AMS
The driver can also be built as a module. If so, the module will be called
xilinx-ams.
+config XUANTIE_TH1520_ADC
+ tristate "XuanTie TH1520 ADC driver"
+ depends on OF
+ depends on HAS_IOMEM
+ depends on ARCH_THEAD
+ default y
+ help
+ Say yes here to support for XuanTie TH1520 MPW analog-to-digital
+ converter.
+
+ This driver can also be built as a module. If so, the module will be
+ called xuantie-th1520-adc.
+
endmenu
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index b3c434722364..820e2a136b33 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -135,4 +135,5 @@ obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
xilinx-xadc-y := xilinx-xadc-core.o xilinx-xadc-events.o
obj-$(CONFIG_XILINX_XADC) += xilinx-xadc.o
obj-$(CONFIG_XILINX_AMS) += xilinx-ams.o
+obj-$(CONFIG_XUANTIE_TH1520_ADC) += xuantie-th1520-adc.o
obj-$(CONFIG_SD_ADC_MODULATOR) += sd_adc_modulator.o
diff --git a/drivers/iio/adc/xuantie-th1520-adc.c b/drivers/iio/adc/xuantie-th1520-adc.c
new file mode 100644
index 000000000000..df0452c2abe7
--- /dev/null
+++ b/drivers/iio/adc/xuantie-th1520-adc.c
@@ -0,0 +1,572 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * XuanTie TH1520 ADC driver
+ *
+ * Copyright (C) 2021-2024 Alibaba Group Holding Limited.
+ * Fugang Duan <duanfugang.dfg@linux.alibaba.com>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_platform.h>
+#include <linux/err.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/sysfs.h>
+
+#include "xuantie-th1520-adc.h"
+
+static inline void th1520_adc_cfg_init(struct th1520_adc *info)
+{
+ struct th1520_adc_feature *adc_feature = &info->adc_feature;
+
+ /* set default Configuration for ADC controller */
+ adc_feature->selres_sel = TH1520_ADC_SELRES_12BIT;
+ adc_feature->input_mode = TH1520_ADC_SINGLE_ENDED_INPUTS;
+ adc_feature->vol_ref = TH1520_ADC_VOL_VREF;
+ adc_feature->offset_mode = TH1520_ADC_OFFSET_DIS;
+ adc_feature->conv_mode = TH1520_ADC_MODE_SINGLE;
+ adc_feature->clk_sel = TH1520_ADC_FCLK_TYP_1M;
+
+ adc_feature->int_actual = TH1520_ADC_ACTUAL_ALL;
+ adc_feature->int_detal = TH1520_ADC_DETAL_ALL;
+
+ info->ch0_offmeas = 0;
+ info->ch1_offmeas = 0;
+}
+
+static void th1520_adc_reg_set(struct th1520_adc *info)
+{
+ u32 phy_cfg = 0;
+ u32 op_ctrl = 0;
+ struct th1520_adc_feature *adc_feature = &info->adc_feature;
+
+ /* phy_cfg */
+ switch (adc_feature->selres_sel) {
+ case TH1520_ADC_SELRES_6BIT:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELRES_6BIT;
+ break;
+ case TH1520_ADC_SELRES_8BIT:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELRES_8BIT;
+ break;
+ case TH1520_ADC_SELRES_10BIT:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELRES_10BIT;
+ break;
+ case TH1520_ADC_SELRES_12BIT:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELRES_12BIT;
+ break;
+ default:
+ break;
+ }
+
+ switch (adc_feature->input_mode) {
+ case TH1520_ADC_SINGLE_ENDED_INPUTS:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELDIFF_SINGLE_ENDED_INPUTS;
+ break;
+ case TH1520_ADC_DIFFERENTIAL_INPUTS:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELDIFF_DIFFERENTIAL_INPUTS;
+ break;
+ default:
+ break;
+ }
+
+ switch (adc_feature->vol_ref) {
+ case TH1520_ADC_VOL_VREF:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELBG_EXTERNAL |
+ TH1520_ADC_PHY_CFG_SELREF_EXT;
+ break;
+ case TH1520_ADC_VOL_INTE:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELBG_INTERNAL |
+ TH1520_ADC_PHY_CFG_SELREF_INTERNAL;
+ break;
+ default:
+ break;
+ }
+
+ /* op_ctrl */
+ switch (adc_feature->conv_mode) {
+ case TH1520_ADC_MODE_SINGLE:
+ op_ctrl |= TH1520_ADC_OP_CTRL_MODE_SINGLE;
+ break;
+ case TH1520_ADC_MODE_CONTINOUS:
+ op_ctrl |= TH1520_ADC_OP_CTRL_MODE_CONTINOUS;
+ break;
+ default:
+ break;
+ }
+
+ writel(phy_cfg, info->regs + TH1520_ADC_PHY_CFG);
+ writel(op_ctrl, info->regs + TH1520_ADC_OP_CTRL);
+
+ writel(TH1520_ADC_PHY_ENCTR, info->regs + TH1520_ADC_PHY_TEST);
+
+ /* disable the irq */
+ writel(0xff, info->regs + TH1520_ADC_INT_CTRL1);
+ writel(0xff, info->regs + TH1520_ADC_INT_CTRL2);
+
+ if (adc_feature->conv_mode == TH1520_ADC_MODE_CONTINOUS)
+ writel(TH1520_ADC_PHY_CTRL_ENADC_EN,
+ info->regs + TH1520_ADC_PHY_CTRL);
+}
+
+static void th1520_adc_fclk_set(struct th1520_adc *info)
+{
+ int fclk_ctrl = 0;
+ int start_time = 0;
+ int sample_time = 0;
+ struct th1520_adc_feature *adc_feature = &info->adc_feature;
+
+ switch (adc_feature->clk_sel) {
+ case TH1520_ADC_FCLK_TYP_1M:
+ fclk_ctrl = TH1520_ADC_FCLK_CTRL_TYP_1M;
+ start_time = TH1520_ADC_START_TIME_TYP_1M;
+ if (adc_feature->selres_sel == TH1520_ADC_SELRES_6BIT)
+ sample_time = TH1520_ADC_SAMPLE_TIME_TYP_6BIT;
+ else if (adc_feature->selres_sel == TH1520_ADC_SELRES_8BIT)
+ sample_time = TH1520_ADC_SAMPLE_TIME_TYP_8BIT;
+ else if (adc_feature->selres_sel == TH1520_ADC_SELRES_10BIT)
+ sample_time = TH1520_ADC_SAMPLE_TIME_TYP_10BIT;
+ else if (adc_feature->selres_sel == TH1520_ADC_SELRES_12BIT)
+ sample_time = TH1520_ADC_SAMPLE_TIME_TYP_12BIT;
+ else {
+ pr_err("[%s,%d]invalid selres select\n",
+ __func__, __LINE__);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ writel(fclk_ctrl, info->regs + TH1520_ADC_FCLK_CTRL);
+ writel(start_time, info->regs + TH1520_ADC_START_TIME);
+ writel(sample_time, info->regs + TH1520_ADC_SAMPLE_TIME);
+}
+
+static void th1520_adc_hw_init(struct th1520_adc *info)
+{
+ th1520_adc_reg_set(info);
+ th1520_adc_fclk_set(info);
+}
+
+static const struct iio_chan_spec th1520_adc_iio_channels[] = {
+ TH1520_ADC_CHAN(0, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(1, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(2, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(3, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(4, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(5, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(6, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(7, IIO_VOLTAGE),
+ /* sentinel */
+};
+
+static irqreturn_t th1520_adc_isr(int irq, void *dev_id)
+{
+ struct iio_dev *indio_dev = dev_id;
+ struct th1520_adc *info = iio_priv(indio_dev);
+ /* TBD */
+ complete(&info->completion);
+ return IRQ_HANDLED;
+}
+
+static int th1520_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ int tmp;
+ long ret;
+ struct th1520_adc *info = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&info->mlock);
+
+ if (info->adc_feature.conv_mode == TH1520_ADC_MODE_SINGLE) {
+ uint ievent;
+ uint vld_flag;
+ uint phy_ctrl;
+ uint real_chan;
+ uint op_ctrl = 0;
+ uint single_retry = TH1520_ADC_FIFO_DATA_SIZE;
+
+ op_ctrl = readl(info->regs + TH1520_ADC_OP_CTRL);
+ op_ctrl &= ~TH1520_ADC_OP_CTRL_CH_EN_ALL;
+ op_ctrl |= (BIT(chan->channel + TH1520_ADC_OP_CTRL_CH_EN_0) &
+ TH1520_ADC_OP_CTRL_CH_EN_ALL);
+ writel(op_ctrl, info->regs + TH1520_ADC_OP_CTRL);
+
+ writel(TH1520_ADC_PHY_CTRL_ENADC_EN,
+ info->regs + TH1520_ADC_PHY_CTRL);
+
+ vld_flag = TH1520_ADC_SAMPLE_DATA_CH0_VLD;
+
+ while (single_retry--) {
+ writel(TH1520_ADC_OP_SINGLE_START_EN,
+ info->regs + TH1520_ADC_OP_SINGLE_START);
+ /* wait the sampling result */
+ ret = readl_poll_timeout(info->regs +
+ TH1520_ADC_SAMPLE_DATA,
+ ievent,
+ ievent & vld_flag, 100,
+ TH1520_ADC_TIMEOUT);
+ if (ret)
+ pr_err("wait the sampling timeout\n");
+
+ real_chan =
+ (ievent & TH1520_ADC_SAMPLE_DATA_CH0_NUMBER) >>
+ TH1520_ADC_SAMPLE_DATA_CH0_NUMBER_OFF;
+ if (real_chan == chan->channel)
+ break;
+ }
+
+ phy_ctrl = readl(info->regs + TH1520_ADC_PHY_CTRL);
+ phy_ctrl &= ~TH1520_ADC_PHY_CTRL_ENADC_EN;
+ writel(phy_ctrl, info->regs + TH1520_ADC_PHY_CTRL);
+
+ /* read the sampling data */
+ *val = (ievent & TH1520_ADC_SAMPLE_DATA_CH0) >>
+ TH1520_ADC_SAMPLE_DATA_CH0_OFF;
+ } else {
+ uint ievent;
+ uint vld_flag;
+ uint op_single;
+ uint op_ctrl = 0;
+
+ op_ctrl = readl(info->regs + TH1520_ADC_OP_CTRL);
+ op_ctrl &= ~TH1520_ADC_OP_CTRL_CH_EN_ALL;
+ op_ctrl |= (BIT(chan->channel + TH1520_ADC_OP_CTRL_CH_EN_0) &
+ TH1520_ADC_OP_CTRL_CH_EN_ALL);
+ writel(op_ctrl, info->regs + TH1520_ADC_OP_CTRL);
+
+ op_single = readl(info->regs +
+ TH1520_ADC_OP_SINGLE_START);
+ op_single &= ~TH1520_ADC_OP_SINGLE_START_EN;
+ writel(op_single,
+ info->regs + TH1520_ADC_OP_SINGLE_START);
+
+ vld_flag = TH1520_ADC_SAMPLE_DATA_CH0_VLD |
+ TH1520_ADC_SAMPLE_DATA_CH1_VLD;
+
+ /* wait the sampling result */
+ ret = readl_poll_timeout(info->regs +
+ TH1520_ADC_SAMPLE_DATA,
+ ievent, ievent & vld_flag, 10,
+ TH1520_ADC_TIMEOUT);
+ if (ret)
+ pr_err("wait the sampling timeout\n");
+
+ /* read the sampling data */
+ tmp = readl(info->regs + TH1520_ADC_SAMPLE_DATA);
+ if (tmp & TH1520_ADC_SAMPLE_DATA_CH0_VLD)
+ *val = (tmp & TH1520_ADC_SAMPLE_DATA_CH0) >>
+ TH1520_ADC_SAMPLE_DATA_CH0_OFF;
+ else
+ *val = (tmp & TH1520_ADC_SAMPLE_DATA_CH1) >>
+ TH1520_ADC_SAMPLE_DATA_CH1_OFF;
+ }
+
+ mutex_unlock(&info->mlock);
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = info->vref_uv / 1000;
+ *val2 = info->adc_feature.selres_sel;
+ return IIO_VAL_FRACTIONAL_LOG2;
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = info->current_clk;
+ *val2 = 0;
+ return IIO_VAL_INT;
+
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static void th1520_adc_reset(struct th1520_adc *info)
+{
+ u32 tmp = readl(info->regs + TH1520_ADC_PHY_CTRL);
+
+ tmp |= TH1520_ADC_PHY_CTRL_RST_EN;
+ writel(tmp, info->regs + TH1520_ADC_PHY_CTRL);
+
+ usleep_range(10, 15);
+
+ tmp &= ~TH1520_ADC_PHY_CTRL_RST_EN;
+ writel(tmp, info->regs + TH1520_ADC_PHY_CTRL);
+}
+
+static void th1520_adc_set_clk(struct th1520_adc *info, int val)
+{
+ u32 count;
+ u32 apb_clk;
+ int fclk_ctrl;
+
+ apb_clk = clk_get_rate(info->clk);
+ count = DIV_ROUND_UP(apb_clk, val);
+ info->current_clk = apb_clk / count;
+
+ fclk_ctrl = readl(info->regs + TH1520_ADC_FCLK_CTRL);
+ fclk_ctrl &= ~TH1520_ADC_FCLK_CTRL_FCLLK_DIV;
+ fclk_ctrl |= count;
+ writel(fclk_ctrl, info->regs + TH1520_ADC_FCLK_CTRL);
+}
+
+static int th1520_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct th1520_adc *info = iio_priv(indio_dev);
+
+ if (mask != IIO_CHAN_INFO_SAMP_FREQ)
+ return -EINVAL;
+
+ mutex_lock(&info->mlock);
+ th1520_adc_set_clk(info, val);
+ mutex_unlock(&info->mlock);
+
+ return 0;
+}
+
+static const struct iio_info th1520_adc_iio_info = {
+ .read_raw = &th1520_read_raw,
+ .write_raw = &th1520_write_raw,
+};
+
+static const struct of_device_id th1520_adc_match[] = {
+ { .compatible = "thead,th1520-adc", },
+ { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, th1520_adc_match);
+
+static ssize_t th1520_adc_res_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ size_t bufpos = 0, count = 5;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct th1520_adc *info = iio_priv(indio_dev);
+
+ snprintf(buf + bufpos, count - bufpos, "%.*x: ", 4,
+ info->adc_feature.selres_sel);
+ bufpos += 4;
+ buf[bufpos++] = '\n';
+
+ return bufpos;
+}
+
+static ssize_t th1520_adc_res_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ unsigned long res;
+ char *start = (char *)buf;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct th1520_adc *info = iio_priv(indio_dev);
+
+ if (kstrtoul(start, 0, &res))
+ return -EINVAL;
+
+ switch (res) {
+ case TH1520_ADC_SELRES_6BIT:
+ case TH1520_ADC_SELRES_8BIT:
+ case TH1520_ADC_SELRES_10BIT:
+ case TH1520_ADC_SELRES_12BIT:
+ info->adc_feature.selres_sel = res;
+ th1520_adc_reset(info);
+ th1520_adc_hw_init(info);
+ break;
+ default:
+ dev_err(dev, "not support res\n");
+ return -EINVAL;
+ }
+
+ return size;
+}
+
+static DEVICE_ATTR_RW(th1520_adc_res);
+
+static int th1520_adc_probe(struct platform_device *pdev)
+{
+ int irq;
+ int ret;
+ struct resource *mem;
+ struct th1520_adc *info;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev,
+ sizeof(struct th1520_adc));
+ if (!indio_dev) {
+ dev_err(&pdev->dev, "Failed allocating iio device\n");
+ return -ENOMEM;
+ }
+
+ info = iio_priv(indio_dev);
+ info->dev = &pdev->dev;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_irq(info->dev, irq, th1520_adc_isr, 0,
+ dev_name(&pdev->dev), indio_dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed requesting irq, irq = %d\n", irq);
+ return ret;
+ }
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ info->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(info->regs))
+ return PTR_ERR(info->regs);
+
+ info->clk = devm_clk_get(&pdev->dev, "adc");
+ if (IS_ERR(info->clk)) {
+ dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
+ PTR_ERR(info->clk));
+ return PTR_ERR(info->clk);
+ }
+
+ info->vref = devm_regulator_get(&pdev->dev, "vref");
+ if (IS_ERR(info->vref))
+ return PTR_ERR(info->vref);
+
+ ret = regulator_enable(info->vref);
+ if (ret)
+ return ret;
+
+ info->vref_uv = regulator_get_voltage(info->vref);
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ mutex_init(&info->mlock);
+ init_completion(&info->completion);
+
+ indio_dev->name = dev_name(&pdev->dev);
+ indio_dev->dev.parent = &pdev->dev;
+ indio_dev->dev.of_node = pdev->dev.of_node;
+ indio_dev->info = &th1520_adc_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = th1520_adc_iio_channels;
+ indio_dev->num_channels = ARRAY_SIZE(th1520_adc_iio_channels);
+
+ ret = clk_prepare_enable(info->clk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Could not prepare or enable the clock.\n");
+ goto error_adc_clk_enable;
+ }
+
+ th1520_adc_cfg_init(info);
+ th1520_adc_reset(info);
+ th1520_adc_hw_init(info);
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Couldn't register the device.\n");
+ goto error_iio_device_register;
+ }
+
+ ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_th1520_adc_res.attr);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to create adc debug sysfs.\n");
+ goto error_iio_device_register;
+ }
+
+ dev_info(&pdev->dev, "XuanTie TH1520 adc registered.\n");
+ return 0;
+
+error_iio_device_register:
+ clk_disable_unprepare(info->clk);
+error_adc_clk_enable:
+ regulator_disable(info->vref);
+
+ return ret;
+}
+
+static int th1520_adc_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct th1520_adc *info = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ regulator_disable(info->vref);
+ clk_disable_unprepare(info->clk);
+
+ return 0;
+}
+
+static int __maybe_unused th1520_adc_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct th1520_adc *info = iio_priv(indio_dev);
+ int phy_ctrl;
+
+ phy_ctrl = readl(info->regs + TH1520_ADC_PHY_CTRL);
+ phy_ctrl &= ~TH1520_ADC_PHY_CTRL_ENADC_EN;
+ writel(phy_ctrl, info->regs + TH1520_ADC_PHY_CTRL);
+
+ clk_disable_unprepare(info->clk);
+ regulator_disable(info->vref);
+
+ return 0;
+}
+
+static int __maybe_unused th1520_adc_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct th1520_adc *info = iio_priv(indio_dev);
+ int ret;
+
+ ret = regulator_enable(info->vref);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(info->clk);
+ if (ret)
+ goto disable_reg;
+
+ th1520_adc_reset(info);
+ th1520_adc_set_clk(info, info->current_clk);
+ th1520_adc_hw_init(info);
+
+ return 0;
+
+disable_reg:
+ regulator_disable(info->vref);
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(th1520_adc_pm_ops,
+ th1520_adc_suspend, th1520_adc_resume);
+
+static struct platform_driver th1520_adc_driver = {
+ .probe = th1520_adc_probe,
+ .remove = th1520_adc_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = th1520_adc_match,
+ .pm = &th1520_adc_pm_ops,
+ },
+};
+module_platform_driver(th1520_adc_driver);
+
+MODULE_AUTHOR("fugang.duan <duanfugang.dfg@linux.alibaba.com>");
+MODULE_DESCRIPTION("XuanTie TH1520 ADC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/xuantie-th1520-adc.h b/drivers/iio/adc/xuantie-th1520-adc.h
new file mode 100644
index 000000000000..c38d17fc6bbe
--- /dev/null
+++ b/drivers/iio/adc/xuantie-th1520-adc.h
@@ -0,0 +1,193 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * XuanTie TH1520 ADC driver
+ *
+ * Copyright (C) 2021-2024 Alibaba Group Holding Limited.
+ * Fugang Duan <duanfugang.dfg@linux.alibaba.com>
+ *
+ */
+
+#include <linux/bitops.h>
+
+/* This will be the driver name the kernel reports */
+#define DRIVER_NAME "xuantie-th1520-adc"
+
+/* ADC registers */
+#define TH1520_ADC_PHY_CFG 0x00
+#define TH1520_ADC_PHY_CTRL 0x04
+#define TH1520_ADC_PHY_TEST 0x08
+#define TH1520_ADC_OP_CTRL 0x0C
+#define TH1520_ADC_OP_SINGLE_START 0x10
+#define TH1520_ADC_FCLK_CTRL 0x14
+#define TH1520_ADC_START_TIME 0x18
+#define TH1520_ADC_SAMPLE_TIME 0x1C
+#define TH1520_ADC_SAMPLE_DATA 0x20
+#define TH1520_ADC_INT_CTRL1 0x50
+#define TH1520_ADC_INT_CTRL2 0x54
+#define TH1520_ADC_INT_STATUS 0x58
+#define TH1520_ADC_INT_ACTUAL_VALUE_CH0 0x60
+#define TH1520_ADC_INT_ACTUAL_VALUE_CH1 0x64
+#define TH1520_ADC_INT_DELTA_VALUE_CH0 0x90
+#define TH1520_ADC_INT_DELTA_VALUE_CH1 0x94
+
+/* Configuration register field define */
+#define TH1520_ADC_PHY_CFG_SELRES_6BIT (0x0)
+#define TH1520_ADC_PHY_CFG_SELRES_8BIT (0x1)
+#define TH1520_ADC_PHY_CFG_SELRES_10BIT (0x2)
+#define TH1520_ADC_PHY_CFG_SELRES_12BIT (0x3)
+#define TH1520_ADC_PHY_CFG_SELDIFF_SINGLE_ENDED_INPUTS (0x0 << 4)
+#define TH1520_ADC_PHY_CFG_SELDIFF_DIFFERENTIAL_INPUTS (0x1 << 4)
+#define TH1520_ADC_PHY_CFG_SELBG_INTERNAL (0x1 << 8)
+#define TH1520_ADC_PHY_CFG_SELBG_EXTERNAL (0x0 << 8)
+#define TH1520_ADC_PHY_CFG_SELREF_INTERNAL (0x1 << 12)
+#define TH1520_ADC_PHY_CFG_SELREF_EXT (0x0 << 12)
+
+/* PHY CTRL register field define */
+#define TH1520_ADC_PHY_CTRL_ENOFFSET_EN (0x1 << 12)
+#define TH1520_ADC_PHY_CTRL_ENOFFMEAS_EN (0x1 << 8)
+#define TH1520_ADC_PHY_CTRL_RST_EN (0x1 << 4)
+#define TH1520_ADC_PHY_CTRL_ENADC_EN (0x1 << 0)
+
+/* ADC OP ctrl field define */
+#define TH1520_ADC_OP_CTRL_CH_EN_ALL GENMASK(19, 12)
+#define TH1520_ADC_OP_CTRL_CH_EN_0 (12)
+#define TH1520_ADC_OP_CTRL_MODE_SINGLE (0x1 << 0)
+#define TH1520_ADC_OP_CTRL_MODE_CONTINOUS (0x0 << 0)
+
+/* ADC OP single start */
+#define TH1520_ADC_OP_SINGLE_START_EN BIT(0)
+
+/* ADC fclk ctrl */
+#define TH1520_ADC_FCLK_CTRL_FCLLK_DIV GENMASK(6, 0)
+#define TH1520_ADC_FCLK_CTRL_TYP_1M (0x10004)
+#define TH1520_ADC_START_TIME_TYP_1M (0x160)
+#define TH1520_ADC_SAMPLE_TIME_TYP_1M (0x10)
+#define TH1520_ADC_SAMPLE_TIME_TYP_6BIT (8)
+#define TH1520_ADC_SAMPLE_TIME_TYP_8BIT (10)
+#define TH1520_ADC_SAMPLE_TIME_TYP_10BIT (12)
+#define TH1520_ADC_SAMPLE_TIME_TYP_12BIT (14)
+
+/* ADC sample data */
+#define TH1520_ADC_SAMPLE_DATA_CH1 GENMASK(27, 16)
+#define TH1520_ADC_SAMPLE_DATA_CH1_OFF (16)
+#define TH1520_ADC_SAMPLE_DATA_CH1_VLD BIT(31)
+#define TH1520_ADC_SAMPLE_DATA_CH1_NUMBER GENMASK(30, 28)
+#define TH1520_ADC_SAMPLE_DATA_CH1_NUMBER_OFF (28)
+#define TH1520_ADC_SAMPLE_DATA_CH0 GENMASK(11, 0)
+#define TH1520_ADC_SAMPLE_DATA_CH0_VLD BIT(15)
+#define TH1520_ADC_SAMPLE_DATA_CH0_OFF (0)
+#define TH1520_ADC_SAMPLE_DATA_CH0_NUMBER GENMASK(14, 12)
+#define TH1520_ADC_SAMPLE_DATA_CH0_NUMBER_OFF (12)
+
+/* ADC INT Ctrl */
+#define TH1520_ADC_INT_CTRL1_CH1_INT_MODE BIT(1)
+#define TH1520_ADC_INT_CTRL1_CH0_INT_MODE BIT(0)
+#define TH1520_ADC_INT_CTRL2_CH1_INT_MASK BIT(1)
+#define TH1520_ADC_INT_CTRL2_CH0_INT_MASK BIT(0)
+#define TH1520_ADC_INT_STS_CH1_INT_STS BIT(1)
+#define TH1520_ADC_INT_STS_CH0_INT_STS BIT(0)
+
+#define TH1520_ADC_ACTUAL_VALUE_CH0_HVAL GENMASK(27, 16)
+#define TH1520_ADC_ACTUAL_VALUE_CH0_HVAL_OFF (16)
+#define TH1520_ADC_ACTUAL_VALUE_CH0_LVAL GENMASK(11, 0)
+#define TH1520_ADC_ACTUAL_VALUE_CH0_LVAL_OFF (0)
+#define TH1520_ADC_ACTUAL_VALUE_CH1_HVAL GENMASK(27, 16)
+#define TH1520_ADC_ACTUAL_VALUE_CH1_HVAL_OFF (16)
+#define TH1520_ADC_ACTUAL_VALUE_CH1_LVAL GENMASK(11, 0)
+#define TH1520_ADC_ACTUAL_VALUE_CH1_LVAL_OFF (0)
+
+#define TH1520_ADC_DLT_VALUE_CH0_HVAL GENMASK(27, 16)
+#define TH1520_ADC_DLT_VALUE_CH0_HVAL_OFF (16)
+#define TH1520_ADC_DLT_VALUE_CH0_LVAL GENMASK(11, 0)
+#define TH1520_ADC_DLT_VALUE_CH0_LVAL_OFF (0)
+#define TH1520_ADC_DLT_VALUE_CH1_HVAL GENMASK(27, 16)
+#define TH1520_ADC_DLT_VALUE_CH1_HVAL_OFF (16)
+#define TH1520_ADC_DLT_VALUE_CH1_LVAL GENMASK(11, 0)
+#define TH1520_ADC_DLT_VALUE_CH1_LVAL_OFF (0)
+
+#define TH1520_ADC_FIFO_DATA_SIZE 32
+#define TH1520_ADC_PHY_ENCTR 0x8e0
+#define TH1520_ADC_TIMEOUT 500000
+
+#define TH1520_ADC_CHAN(_idx, _chan_type) { \
+ .type = (_chan_type), \
+ .indexed = 1, \
+ .channel = (_idx), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+}
+
+enum vol_ref {
+ TH1520_ADC_VOL_VREF,
+ TH1520_ADC_VOL_INTE,
+};
+
+enum input_mode_sel {
+ TH1520_ADC_SINGLE_ENDED_INPUTS,
+ TH1520_ADC_DIFFERENTIAL_INPUTS,
+};
+
+enum selres_sel {
+ TH1520_ADC_SELRES_6BIT = 6,
+ TH1520_ADC_SELRES_8BIT = 8,
+ TH1520_ADC_SELRES_10BIT = 10,
+ TH1520_ADC_SELRES_12BIT = 12,
+};
+
+enum offset_mode_sel {
+ TH1520_ADC_OFFSET_DIS = 0,
+ TH1520_ADC_OFFSET_EN,
+};
+
+enum conversion_mode_sel {
+ TH1520_ADC_MODE_SINGLE,
+ TH1520_ADC_MODE_CONTINOUS,
+};
+
+enum clk_sel {
+ TH1520_ADC_FCLK_TYP_1M,
+};
+
+enum int_actual_mask {
+ TH1520_ADC_ACTUAL_CH0,
+ TH1520_ADC_ACTUAL_CH1,
+ TH1520_ADC_ACTUAL_ALL,
+
+};
+
+enum int_delta_mask {
+ TH1520_ADC_DETAL_CH0,
+ TH1520_ADC_DETAL_CH1,
+ TH1520_ADC_DETAL_ALL,
+};
+
+struct th1520_adc_feature {
+ enum selres_sel selres_sel;
+ enum input_mode_sel input_mode;
+ enum vol_ref vol_ref;
+ enum offset_mode_sel offset_mode;
+ enum conversion_mode_sel conv_mode;
+ enum clk_sel clk_sel;
+ enum int_actual_mask int_actual;
+ enum int_delta_mask int_detal;
+};
+
+struct th1520_adc {
+ struct device *dev;
+ void __iomem *regs;
+ struct clk *clk;
+
+ u32 vref_uv;
+ u32 value;
+ struct regulator *vref;
+ struct th1520_adc_feature adc_feature;
+ u32 current_clk;
+ u32 ch0_offmeas;
+ u32 ch1_offmeas;
+
+ struct completion completion;
+ /* lock to protect against multiple access to the device */
+ struct mutex mlock;
+};
+
--
2.44.0
^ permalink raw reply related
* [PATCH 2/3] riscv: dts: thead: Add XuanTie TH1520 ADC device node
From: wefu @ 2024-03-29 20:01 UTC (permalink / raw)
To: jszhang, guoren, conor, robh, krzysztof.kozlowski+dt,
paul.walmsley, palmer, aou, jic23, lars, andriy.shevchenko,
nuno.sa, marcelo.schmitt, bigunclemax, marius.cristea, fr0st61te,
okan.sahin, marcus.folkesson, schnelle, lee, mike.looijmans
Cc: linux-riscv, devicetree, linux-kernel, linux-iio, Wei Fu
In-Reply-To: <20240329200241.4122000-1-wefu@redhat.com>
From: Wei Fu <wefu@redhat.com>
Add nodes for the XuanTie TH1520 ADC device node on the XuanTie TH1520 Soc.
Signed-off-by: Wei Fu <wefu@redhat.com>
---
arch/riscv/boot/dts/thead/th1520.dtsi | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi
index b7b7e950a7d7..cd6bc89a240c 100644
--- a/arch/riscv/boot/dts/thead/th1520.dtsi
+++ b/arch/riscv/boot/dts/thead/th1520.dtsi
@@ -417,6 +417,16 @@ uart5: serial@fff7f0c000 {
status = "disabled";
};
+ adc: adc@0xfffff51000 {
+ compatible = "thead,th1520-adc";
+ reg = <0xff 0xfff51000 0x0 0x1000>;
+ interrupts = <61 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&aonsys_clk>;
+ clock-names = "adc";
+ /* ADC pin is proprietary,no need to config pinctrl */
+ status = "disabled";
+ };
+
timer4: timer@ffffc33000 {
compatible = "snps,dw-apb-timer";
reg = <0xff 0xffc33000 0x0 0x14>;
--
2.44.0
^ permalink raw reply related
* [PATCH 3/3] dt-bindings: adc: Document XuanTie TH1520 ADC
From: wefu @ 2024-03-29 20:01 UTC (permalink / raw)
To: jszhang, guoren, conor, robh, krzysztof.kozlowski+dt,
paul.walmsley, palmer, aou, jic23, lars, andriy.shevchenko,
nuno.sa, marcelo.schmitt, bigunclemax, marius.cristea, fr0st61te,
okan.sahin, marcus.folkesson, schnelle, lee, mike.looijmans
Cc: linux-riscv, devicetree, linux-kernel, linux-iio, Wei Fu
In-Reply-To: <20240329200241.4122000-1-wefu@redhat.com>
From: Wei Fu <wefu@redhat.com>
Document devicetree bindings for the XuanTie TH1520 AP sub-system ADC.
Signed-off-by: Wei Fu <wefu@redhat.com>
---
.../bindings/iio/adc/thead,th1520.yaml | 52 +++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/thead,th1520.yaml b/Documentation/devicetree/bindings/iio/adc/thead,th1520.yaml
new file mode 100644
index 000000000000..80890ce62810
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/thead,th1520.yaml
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/thead,th1520.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: XuanTie TH1520 Analog to Digital Converter (ADC)
+
+maintainers:
+ - Fugang Duan <duanfugang.dfg@linux.alibaba.com>
+ - Wei Fu <wefu@redhat.com>
+
+description: |
+ 12-Bit Analog to Digital Converter (ADC) on XuanTie TH1520
+
+properties:
+ compatible:
+ const: thead,th1520
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ const: adc
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+ - status
+
+additionalProperties: false
+
+examples:
+ - |
+ adc: adc@0xfffff51000 {
+ compatible = "thead,th1520-adc";
+ reg = <0xff 0xfff51000 0x0 0x1000>;
+ interrupts = <61 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&aonsys_clk>;
+ clock-names = "adc";
+ /* ADC pin is proprietary,no need to config pinctrl */
+ status = "disabled";
+ };
--
2.44.0
^ permalink raw reply related
* [PATCH v1 0/6] clk: meson: introduce Amlogic A1 SoC Family CPU clock controller driver
From: Dmitry Rokosov @ 2024-03-29 20:58 UTC (permalink / raw)
To: neil.armstrong, jbrunet, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl
Cc: kernel, rockosov, linux-amlogic, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, Dmitry Rokosov
The CPU clock controller plays a general role in the Amlogic A1 SoC
family by generating CPU clocks. As an APB slave module, it offers the
capability to inherit the CPU clock from two sources: the internal fixed
clock known as 'cpu fixed clock' and the external input provided by the
A1 PLL clock controller, referred to as 'syspll'.
It is important for the driver to handle the cpu_clk rate switching
effectively by transitioning to the CPU fixed clock to avoid any
potential execution freezes.
Validation:
* to double-check all clk flags, run the below helper script:
```
pushd /sys/kernel/debug/clk
for f in *; do
if [[ -f "$f/clk_flags" ]]; then
flags="$(cat $f/clk_flags | awk '{$1=$1};1' | sed ':a;N;$!ba;s/\n/ | /g')"
echo -e "$f: $flags"
fi
done
popd
```
* to trace the current clks state, use the
'/sys/kernel/debug/clk/clk_dump' node with jq post-processing:
```
$ cat /sys/kernel/debug/clk/clk_dump | jq '.' > clk_dump.json
```
* to see the CPU clock hierarchy, use the
'/sys/kernel/debug/clk/clk_summary' node with jq post-processing:
```
$ cat /sys/kernel/debug/clk/clk_summary | jq '.' > clk_dump.json
```
when cpu_clk is inherited from sys_pll, it should be:
```
syspll_in 1 1 0 24000000 0 0 50000 Y deviceless no_connection_id
sys_pll 2 2 0 1200000000 0 0 50000 Y deviceless no_connection_id
cpu_clk 1 1 0 1200000000 0 0 50000 Y cpu0 no_connection_id
cpu0 no_connection_id
fd000000.clock-controller dvfs
deviceless no_connection_id
```
and from cpu fixed clock:
```
fclk_div3_div 1 1 0 512000000 0 0 50000 Y deviceless no_connection_id
fclk_div3 4 4 0 512000000 0 0 50000 Y deviceless no_connection_id
cpu_fsource_sel0 1 1 0 512000000 0 0 50000 Y deviceless no_connection_id
cpu_fsource_div0 1 1 0 128000000 0 0 50000 Y deviceless no_connection_id
cpu_fsel0 1 1 0 128000000 0 0 50000 Y deviceless no_connection_id
cpu_fclk 1 1 0 128000000 0 0 50000 Y deviceless no_connection_id
cpu_clk 1 1 0 128000000 0 0 50000 Y cpu0 no_connection_id
cpu0 no_connection_id
fd000000.clock-controller dvfs
deviceless no_connection_id
```
* to debug cpu clk rate propagation and proper parent switching, compile
kernel with the following definition:
$ sed -i "s/undef CLOCK_ALLOW_WRITE_DEBUGFS/define CLOCK_ALLOW_WRITE_DEBUGFS/g" drivers/clk/clk.c
after that, clk_rate debug node for each clock will be available for
write operation
Dmitry Rokosov (6):
dt-bindings: clock: meson: a1: pll: introduce new syspll bindings
clk: meson: a1: pll: support 'syspll' general-purpose PLL for CPU
clock
dt-bindings: clock: meson: a1: peripherals: support sys_pll_div16
input
clk: meson: a1: peripherals: support 'sys_pll_div16' clock as GEN
input
dt-bindings: clock: meson: add A1 CPU clock controller bindings
clk: meson: a1: add Amlogic A1 CPU clock controller driver
.../bindings/clock/amlogic,a1-cpu-clkc.yaml | 64 ++++
.../clock/amlogic,a1-peripherals-clkc.yaml | 5 +-
.../bindings/clock/amlogic,a1-pll-clkc.yaml | 7 +-
drivers/clk/meson/Kconfig | 10 +
drivers/clk/meson/Makefile | 1 +
drivers/clk/meson/a1-cpu.c | 324 ++++++++++++++++++
drivers/clk/meson/a1-cpu.h | 16 +
drivers/clk/meson/a1-peripherals.c | 4 +-
drivers/clk/meson/a1-pll.c | 78 +++++
drivers/clk/meson/a1-pll.h | 6 +
.../dt-bindings/clock/amlogic,a1-cpu-clkc.h | 19 +
.../dt-bindings/clock/amlogic,a1-pll-clkc.h | 2 +
12 files changed, 531 insertions(+), 5 deletions(-)
create mode 100644 Documentation/devicetree/bindings/clock/amlogic,a1-cpu-clkc.yaml
create mode 100644 drivers/clk/meson/a1-cpu.c
create mode 100644 drivers/clk/meson/a1-cpu.h
create mode 100644 include/dt-bindings/clock/amlogic,a1-cpu-clkc.h
--
2.43.0
^ permalink raw reply
* [PATCH v1 1/6] dt-bindings: clock: meson: a1: pll: introduce new syspll bindings
From: Dmitry Rokosov @ 2024-03-29 20:58 UTC (permalink / raw)
To: neil.armstrong, jbrunet, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl
Cc: kernel, rockosov, linux-amlogic, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, Dmitry Rokosov
In-Reply-To: <20240329205904.25002-1-ddrokosov@salutedevices.com>
The 'syspll' PLL is a general-purpose PLL designed specifically for the
CPU clock. It is capable of producing output frequencies within the
range of 768MHz to 1536MHz.
Signed-off-by: Dmitry Rokosov <ddrokosov@salutedevices.com>
---
.../devicetree/bindings/clock/amlogic,a1-pll-clkc.yaml | 7 +++++--
include/dt-bindings/clock/amlogic,a1-pll-clkc.h | 2 ++
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/clock/amlogic,a1-pll-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a1-pll-clkc.yaml
index a59b188a8bf5..fbba57031278 100644
--- a/Documentation/devicetree/bindings/clock/amlogic,a1-pll-clkc.yaml
+++ b/Documentation/devicetree/bindings/clock/amlogic,a1-pll-clkc.yaml
@@ -26,11 +26,13 @@ properties:
items:
- description: input fixpll_in
- description: input hifipll_in
+ - description: input syspll_in
clock-names:
items:
- const: fixpll_in
- const: hifipll_in
+ - const: syspll_in
required:
- compatible
@@ -53,7 +55,8 @@ examples:
reg = <0 0x7c80 0 0x18c>;
#clock-cells = <1>;
clocks = <&clkc_periphs CLKID_FIXPLL_IN>,
- <&clkc_periphs CLKID_HIFIPLL_IN>;
- clock-names = "fixpll_in", "hifipll_in";
+ <&clkc_periphs CLKID_HIFIPLL_IN>,
+ <&clkc_periphs CLKID_SYSPLL_IN>;
+ clock-names = "fixpll_in", "hifipll_in", "syspll_in";
};
};
diff --git a/include/dt-bindings/clock/amlogic,a1-pll-clkc.h b/include/dt-bindings/clock/amlogic,a1-pll-clkc.h
index 2b660c0f2c9f..a702d610589c 100644
--- a/include/dt-bindings/clock/amlogic,a1-pll-clkc.h
+++ b/include/dt-bindings/clock/amlogic,a1-pll-clkc.h
@@ -21,5 +21,7 @@
#define CLKID_FCLK_DIV5 8
#define CLKID_FCLK_DIV7 9
#define CLKID_HIFI_PLL 10
+#define CLKID_SYS_PLL 11
+#define CLKID_SYS_PLL_DIV16 12
#endif /* __A1_PLL_CLKC_H */
--
2.43.0
^ permalink raw reply related
* [PATCH v1 2/6] clk: meson: a1: pll: support 'syspll' general-purpose PLL for CPU clock
From: Dmitry Rokosov @ 2024-03-29 20:58 UTC (permalink / raw)
To: neil.armstrong, jbrunet, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl
Cc: kernel, rockosov, linux-amlogic, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, Dmitry Rokosov
In-Reply-To: <20240329205904.25002-1-ddrokosov@salutedevices.com>
The 'syspll' PLL, also known as the system PLL, is a general and
essential PLL responsible for generating the CPU clock frequency.
With its wide-ranging capabilities, it is designed to accommodate
frequencies within the range of 768MHz to 1536MHz.
Signed-off-by: Dmitry Rokosov <ddrokosov@salutedevices.com>
---
drivers/clk/meson/a1-pll.c | 78 ++++++++++++++++++++++++++++++++++++++
drivers/clk/meson/a1-pll.h | 6 +++
2 files changed, 84 insertions(+)
diff --git a/drivers/clk/meson/a1-pll.c b/drivers/clk/meson/a1-pll.c
index 60b2e53e7e51..02fd2d325cc6 100644
--- a/drivers/clk/meson/a1-pll.c
+++ b/drivers/clk/meson/a1-pll.c
@@ -138,6 +138,81 @@ static struct clk_regmap hifi_pll = {
},
};
+static const struct pll_mult_range sys_pll_mult_range = {
+ .min = 32,
+ .max = 64,
+};
+
+/*
+ * We assume that the sys_pll_clk has already been set up by the low-level
+ * bootloaders as the main CPU PLL source. Therefore, it is not necessary to
+ * run the initialization sequence.
+ */
+static struct clk_regmap sys_pll = {
+ .data = &(struct meson_clk_pll_data){
+ .en = {
+ .reg_off = ANACTRL_SYSPLL_CTRL0,
+ .shift = 28,
+ .width = 1,
+ },
+ .m = {
+ .reg_off = ANACTRL_SYSPLL_CTRL0,
+ .shift = 0,
+ .width = 8,
+ },
+ .n = {
+ .reg_off = ANACTRL_SYSPLL_CTRL0,
+ .shift = 10,
+ .width = 5,
+ },
+ .frac = {
+ .reg_off = ANACTRL_SYSPLL_CTRL1,
+ .shift = 0,
+ .width = 19,
+ },
+ .l = {
+ .reg_off = ANACTRL_SYSPLL_STS,
+ .shift = 31,
+ .width = 1,
+ },
+ .current_en = {
+ .reg_off = ANACTRL_SYSPLL_CTRL0,
+ .shift = 26,
+ .width = 1,
+ },
+ .l_detect = {
+ .reg_off = ANACTRL_SYSPLL_CTRL2,
+ .shift = 6,
+ .width = 1,
+ },
+ .range = &sys_pll_mult_range,
+ },
+ .hw.init = &(struct clk_init_data){
+ .name = "sys_pll",
+ .ops = &meson_clk_pll_ops,
+ .parent_names = (const char *[]){ "syspll_in" },
+ .num_parents = 1,
+ /*
+ * This clock is used as the main CPU PLL source in low-level
+ * bootloaders, and it is necessary to mark it as critical.
+ */
+ .flags = CLK_IS_CRITICAL,
+ },
+};
+
+static struct clk_fixed_factor sys_pll_div16 = {
+ .mult = 1,
+ .div = 16,
+ .hw.init = &(struct clk_init_data){
+ .name = "sys_pll_div16",
+ .ops = &clk_fixed_factor_ops,
+ .parent_hws = (const struct clk_hw *[]) {
+ &sys_pll.hw
+ },
+ .num_parents = 1,
+ },
+};
+
static struct clk_fixed_factor fclk_div2_div = {
.mult = 1,
.div = 2,
@@ -283,6 +358,8 @@ static struct clk_hw *a1_pll_hw_clks[] = {
[CLKID_FCLK_DIV5] = &fclk_div5.hw,
[CLKID_FCLK_DIV7] = &fclk_div7.hw,
[CLKID_HIFI_PLL] = &hifi_pll.hw,
+ [CLKID_SYS_PLL] = &sys_pll.hw,
+ [CLKID_SYS_PLL_DIV16] = &sys_pll_div16.hw,
};
static struct clk_regmap *const a1_pll_regmaps[] = {
@@ -293,6 +370,7 @@ static struct clk_regmap *const a1_pll_regmaps[] = {
&fclk_div5,
&fclk_div7,
&hifi_pll,
+ &sys_pll,
};
static struct regmap_config a1_pll_regmap_cfg = {
diff --git a/drivers/clk/meson/a1-pll.h b/drivers/clk/meson/a1-pll.h
index 4be17b2bf383..666d9b2137e9 100644
--- a/drivers/clk/meson/a1-pll.h
+++ b/drivers/clk/meson/a1-pll.h
@@ -18,6 +18,12 @@
#define ANACTRL_FIXPLL_CTRL0 0x0
#define ANACTRL_FIXPLL_CTRL1 0x4
#define ANACTRL_FIXPLL_STS 0x14
+#define ANACTRL_SYSPLL_CTRL0 0x80
+#define ANACTRL_SYSPLL_CTRL1 0x84
+#define ANACTRL_SYSPLL_CTRL2 0x88
+#define ANACTRL_SYSPLL_CTRL3 0x8c
+#define ANACTRL_SYSPLL_CTRL4 0x90
+#define ANACTRL_SYSPLL_STS 0x94
#define ANACTRL_HIFIPLL_CTRL0 0xc0
#define ANACTRL_HIFIPLL_CTRL1 0xc4
#define ANACTRL_HIFIPLL_CTRL2 0xc8
--
2.43.0
^ permalink raw reply related
* [PATCH v1 3/6] dt-bindings: clock: meson: a1: peripherals: support sys_pll_div16 input
From: Dmitry Rokosov @ 2024-03-29 20:58 UTC (permalink / raw)
To: neil.armstrong, jbrunet, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl
Cc: kernel, rockosov, linux-amlogic, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, Dmitry Rokosov
In-Reply-To: <20240329205904.25002-1-ddrokosov@salutedevices.com>
The 'sys_pll_div16' input clock is used as one of the sources for the
GEN clock.
Signed-off-by: Dmitry Rokosov <ddrokosov@salutedevices.com>
---
.../bindings/clock/amlogic,a1-peripherals-clkc.yaml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/clock/amlogic,a1-peripherals-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a1-peripherals-clkc.yaml
index 6d84cee1bd75..f6668991ff1f 100644
--- a/Documentation/devicetree/bindings/clock/amlogic,a1-peripherals-clkc.yaml
+++ b/Documentation/devicetree/bindings/clock/amlogic,a1-peripherals-clkc.yaml
@@ -29,6 +29,7 @@ properties:
- description: input fixed pll div5
- description: input fixed pll div7
- description: input hifi pll
+ - description: input sys pll div16
- description: input oscillator (usually at 24MHz)
clock-names:
@@ -38,6 +39,7 @@ properties:
- const: fclk_div5
- const: fclk_div7
- const: hifi_pll
+ - const: sys_pll_div16
- const: xtal
required:
@@ -65,9 +67,10 @@ examples:
<&clkc_pll CLKID_FCLK_DIV5>,
<&clkc_pll CLKID_FCLK_DIV7>,
<&clkc_pll CLKID_HIFI_PLL>,
+ <&clkc_pll CLKID_SYS_PLL_DIV16>,
<&xtal>;
clock-names = "fclk_div2", "fclk_div3",
"fclk_div5", "fclk_div7",
- "hifi_pll", "xtal";
+ "hifi_pll", "sys_pll_div16", "xtal";
};
};
--
2.43.0
^ permalink raw reply related
* [PATCH v1 4/6] clk: meson: a1: peripherals: support 'sys_pll_div16' clock as GEN input
From: Dmitry Rokosov @ 2024-03-29 20:58 UTC (permalink / raw)
To: neil.armstrong, jbrunet, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl
Cc: kernel, rockosov, linux-amlogic, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, Dmitry Rokosov
In-Reply-To: <20240329205904.25002-1-ddrokosov@salutedevices.com>
The clock 'sys_pll_div16' is one of the parents of the GEN clock. It is
generated in the A1 PLL clock controller with a fixed factor.
Signed-off-by: Dmitry Rokosov <ddrokosov@salutedevices.com>
---
drivers/clk/meson/a1-peripherals.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/clk/meson/a1-peripherals.c b/drivers/clk/meson/a1-peripherals.c
index 621af1e6e4b2..3c4452f2b146 100644
--- a/drivers/clk/meson/a1-peripherals.c
+++ b/drivers/clk/meson/a1-peripherals.c
@@ -747,13 +747,13 @@ static struct clk_regmap fclk_div2_divn = {
};
/*
- * the index 2 is sys_pll_div16, it will be implemented in the CPU clock driver,
* the index 4 is the clock measurement source, it's not supported yet
*/
-static u32 gen_table[] = { 0, 1, 3, 5, 6, 7, 8 };
+static u32 gen_table[] = { 0, 1, 2, 3, 5, 6, 7, 8 };
static const struct clk_parent_data gen_parent_data[] = {
{ .fw_name = "xtal", },
{ .hw = &rtc.hw },
+ { .fw_name = "sys_pll_div16", },
{ .fw_name = "hifi_pll", },
{ .fw_name = "fclk_div2", },
{ .fw_name = "fclk_div3", },
--
2.43.0
^ permalink raw reply related
* [PATCH v1 5/6] dt-bindings: clock: meson: add A1 CPU clock controller bindings
From: Dmitry Rokosov @ 2024-03-29 20:58 UTC (permalink / raw)
To: neil.armstrong, jbrunet, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl
Cc: kernel, rockosov, linux-amlogic, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, Dmitry Rokosov
In-Reply-To: <20240329205904.25002-1-ddrokosov@salutedevices.com>
Add the documentation and dt bindings for Amlogic A1 CPU clock
controller.
This controller consists of the general 'cpu_clk' and two main parents:
'cpu fixed clock' and 'syspll'. The 'cpu fixed clock' is an internal
fixed clock, while the 'syspll' serves as an external input from the A1
PLL clock controller.
Signed-off-by: Dmitry Rokosov <ddrokosov@salutedevices.com>
---
.../bindings/clock/amlogic,a1-cpu-clkc.yaml | 64 +++++++++++++++++++
.../dt-bindings/clock/amlogic,a1-cpu-clkc.h | 19 ++++++
2 files changed, 83 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/amlogic,a1-cpu-clkc.yaml
create mode 100644 include/dt-bindings/clock/amlogic,a1-cpu-clkc.h
diff --git a/Documentation/devicetree/bindings/clock/amlogic,a1-cpu-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a1-cpu-clkc.yaml
new file mode 100644
index 000000000000..d52d2e084ae7
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/amlogic,a1-cpu-clkc.yaml
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/amlogic,a1-cpu-clkc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic A1 CPU Clock Control Unit
+
+maintainers:
+ - Neil Armstrong <neil.armstrong@linaro.org>
+ - Jerome Brunet <jbrunet@baylibre.com>
+ - Dmitry Rokosov <ddrokosov@salutedevices.com>
+
+properties:
+ compatible:
+ const: amlogic,a1-cpu-clkc
+
+ '#clock-cells':
+ const: 1
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: input fixed pll div2
+ - description: input fixed pll div3
+ - description: input sys pll
+ - description: input oscillator (usually at 24MHz)
+
+ clock-names:
+ items:
+ - const: fclk_div2
+ - const: fclk_div3
+ - const: sys_pll
+ - const: xtal
+
+required:
+ - compatible
+ - '#clock-cells'
+ - reg
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/amlogic,a1-pll-clkc.h>
+ apb {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ clock-controller@fd000000 {
+ compatible = "amlogic,a1-cpu-clkc";
+ reg = <0 0xfd000000 0 0x88>;
+ #clock-cells = <1>;
+ clocks = <&clkc_pll CLKID_FCLK_DIV2>,
+ <&clkc_pll CLKID_FCLK_DIV3>,
+ <&clkc_pll CLKID_SYS_PLL>,
+ <&xtal>;
+ clock-names = "fclk_div2", "fclk_div3", "sys_pll", "xtal";
+ };
+ };
diff --git a/include/dt-bindings/clock/amlogic,a1-cpu-clkc.h b/include/dt-bindings/clock/amlogic,a1-cpu-clkc.h
new file mode 100644
index 000000000000..1d321c6eddb7
--- /dev/null
+++ b/include/dt-bindings/clock/amlogic,a1-cpu-clkc.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
+/*
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ * Author: Dmitry Rokosov <ddrokosov@salutedevices.com>
+ */
+
+#ifndef __A1_CPU_CLKC_H
+#define __A1_CPU_CLKC_H
+
+#define CLKID_CPU_FSOURCE_SEL0 0
+#define CLKID_CPU_FSOURCE_DIV0 1
+#define CLKID_CPU_FSEL0 2
+#define CLKID_CPU_FSOURCE_SEL1 3
+#define CLKID_CPU_FSOURCE_DIV1 4
+#define CLKID_CPU_FSEL1 5
+#define CLKID_CPU_FCLK 6
+#define CLKID_CPU_CLK 7
+
+#endif /* __A1_CPU_CLKC_H */
--
2.43.0
^ permalink raw reply related
* [PATCH v1 6/6] clk: meson: a1: add Amlogic A1 CPU clock controller driver
From: Dmitry Rokosov @ 2024-03-29 20:58 UTC (permalink / raw)
To: neil.armstrong, jbrunet, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl
Cc: kernel, rockosov, linux-amlogic, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, Dmitry Rokosov
In-Reply-To: <20240329205904.25002-1-ddrokosov@salutedevices.com>
The CPU clock controller plays a general role in the Amlogic A1 SoC
family by generating CPU clocks. As an APB slave module, it offers the
capability to inherit the CPU clock from two sources: the internal fixed
clock known as 'cpu fixed clock' and the external input provided by the
A1 PLL clock controller, referred to as 'syspll'.
It is important for the driver to handle cpu_clk rate switching
effectively by transitioning to the CPU fixed clock to avoid any
potential execution freezes.
Signed-off-by: Dmitry Rokosov <ddrokosov@salutedevices.com>
---
drivers/clk/meson/Kconfig | 10 ++
drivers/clk/meson/Makefile | 1 +
drivers/clk/meson/a1-cpu.c | 324 +++++++++++++++++++++++++++++++++++++
drivers/clk/meson/a1-cpu.h | 16 ++
4 files changed, 351 insertions(+)
create mode 100644 drivers/clk/meson/a1-cpu.c
create mode 100644 drivers/clk/meson/a1-cpu.h
diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig
index 80c4a18c83d2..148d4495eee3 100644
--- a/drivers/clk/meson/Kconfig
+++ b/drivers/clk/meson/Kconfig
@@ -111,6 +111,16 @@ config COMMON_CLK_AXG_AUDIO
Support for the audio clock controller on AmLogic A113D devices,
aka axg, Say Y if you want audio subsystem to work.
+config COMMON_CLK_A1_CPU
+ tristate "Amlogic A1 SoC CPU controller support"
+ depends on ARM64
+ select COMMON_CLK_MESON_REGMAP
+ select COMMON_CLK_MESON_CLKC_UTILS
+ help
+ Support for the CPU clock controller on Amlogic A113L based
+ device, A1 SoC Family. Say Y if you want A1 CPU clock controller
+ to work.
+
config COMMON_CLK_A1_PLL
tristate "Amlogic A1 SoC PLL controller support"
depends on ARM64
diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile
index 4968fc7ad555..2a06eb0303d6 100644
--- a/drivers/clk/meson/Makefile
+++ b/drivers/clk/meson/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_COMMON_CLK_MESON_AUDIO_RSTC) += meson-audio-rstc.o
obj-$(CONFIG_COMMON_CLK_AXG) += axg.o axg-aoclk.o
obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o
+obj-$(CONFIG_COMMON_CLK_A1_CPU) += a1-cpu.o
obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o
obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o
obj-$(CONFIG_COMMON_CLK_A1_AUDIO) += a1-audio.o
diff --git a/drivers/clk/meson/a1-cpu.c b/drivers/clk/meson/a1-cpu.c
new file mode 100644
index 000000000000..5f5d8ae112e5
--- /dev/null
+++ b/drivers/clk/meson/a1-cpu.c
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Amlogic A1 SoC family CPU Clock Controller driver.
+ *
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ * Author: Dmitry Rokosov <ddrokosov@salutedevices.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include "a1-cpu.h"
+#include "clk-regmap.h"
+#include "meson-clkc-utils.h"
+
+#include <dt-bindings/clock/amlogic,a1-cpu-clkc.h>
+
+static u32 cpu_fsource_sel_table[] = { 0, 1, 2 };
+static const struct clk_parent_data cpu_fsource_sel_parents[] = {
+ { .fw_name = "xtal" },
+ { .fw_name = "fclk_div2" },
+ { .fw_name = "fclk_div3" },
+};
+
+static struct clk_regmap cpu_fsource_sel0 = {
+ .data = &(struct clk_regmap_mux_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .mask = 0x3,
+ .shift = 0,
+ .table = cpu_fsource_sel_table,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fsource_sel0",
+ .ops = &clk_regmap_mux_ops,
+ .parent_data = cpu_fsource_sel_parents,
+ .num_parents = ARRAY_SIZE(cpu_fsource_sel_parents),
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_fsource_div0 = {
+ .data = &(struct clk_regmap_div_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .shift = 4,
+ .width = 6,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fsource_div0",
+ .ops = &clk_regmap_divider_ops,
+ .parent_hws = (const struct clk_hw *[]) {
+ &cpu_fsource_sel0.hw
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_fsel0 = {
+ .data = &(struct clk_regmap_mux_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .mask = 0x1,
+ .shift = 2,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fsel0",
+ .ops = &clk_regmap_mux_ops,
+ .parent_hws = (const struct clk_hw *[]) {
+ &cpu_fsource_sel0.hw,
+ &cpu_fsource_div0.hw,
+ },
+ .num_parents = 2,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_fsource_sel1 = {
+ .data = &(struct clk_regmap_mux_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .mask = 0x3,
+ .shift = 16,
+ .table = cpu_fsource_sel_table,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fsource_sel1",
+ .ops = &clk_regmap_mux_ops,
+ .parent_data = cpu_fsource_sel_parents,
+ .num_parents = ARRAY_SIZE(cpu_fsource_sel_parents),
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_fsource_div1 = {
+ .data = &(struct clk_regmap_div_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .shift = 20,
+ .width = 6,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fsource_div1",
+ .ops = &clk_regmap_divider_ops,
+ .parent_hws = (const struct clk_hw *[]) {
+ &cpu_fsource_sel1.hw
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_fsel1 = {
+ .data = &(struct clk_regmap_mux_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .mask = 0x1,
+ .shift = 18,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fsel1",
+ .ops = &clk_regmap_mux_ops,
+ .parent_hws = (const struct clk_hw *[]) {
+ &cpu_fsource_sel1.hw,
+ &cpu_fsource_div1.hw,
+ },
+ .num_parents = 2,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_fclk = {
+ .data = &(struct clk_regmap_mux_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .mask = 0x1,
+ .shift = 10,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fclk",
+ .ops = &clk_regmap_mux_ops,
+ .parent_hws = (const struct clk_hw *[]) {
+ &cpu_fsel0.hw,
+ &cpu_fsel1.hw,
+ },
+ .num_parents = 2,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_clk = {
+ .data = &(struct clk_regmap_mux_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .mask = 0x1,
+ .shift = 11,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_clk",
+ .ops = &clk_regmap_mux_ops,
+ .parent_data = (const struct clk_parent_data []) {
+ { .hw = &cpu_fclk.hw },
+ { .fw_name = "sys_pll", },
+ },
+ .num_parents = 2,
+ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
+ },
+};
+
+/* Array of all clocks registered by this provider */
+static struct clk_hw *a1_cpu_hw_clks[] = {
+ [CLKID_CPU_FSOURCE_SEL0] = &cpu_fsource_sel0.hw,
+ [CLKID_CPU_FSOURCE_DIV0] = &cpu_fsource_div0.hw,
+ [CLKID_CPU_FSEL0] = &cpu_fsel0.hw,
+ [CLKID_CPU_FSOURCE_SEL1] = &cpu_fsource_sel1.hw,
+ [CLKID_CPU_FSOURCE_DIV1] = &cpu_fsource_div1.hw,
+ [CLKID_CPU_FSEL1] = &cpu_fsel1.hw,
+ [CLKID_CPU_FCLK] = &cpu_fclk.hw,
+ [CLKID_CPU_CLK] = &cpu_clk.hw,
+};
+
+static struct clk_regmap *const a1_cpu_regmaps[] = {
+ &cpu_fsource_sel0,
+ &cpu_fsource_div0,
+ &cpu_fsel0,
+ &cpu_fsource_sel1,
+ &cpu_fsource_div1,
+ &cpu_fsel1,
+ &cpu_fclk,
+ &cpu_clk,
+};
+
+static struct regmap_config a1_cpu_regmap_cfg = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = CPUCTRL_CLK_CTRL1,
+};
+
+static struct meson_clk_hw_data a1_cpu_clks = {
+ .hws = a1_cpu_hw_clks,
+ .num = ARRAY_SIZE(a1_cpu_hw_clks),
+};
+
+struct a1_cpu_clk_nb_data {
+ const struct clk_ops *mux_ops;
+ struct clk_hw *cpu_clk;
+ struct notifier_block nb;
+ u8 parent;
+};
+
+#define MESON_A1_CPU_CLK_GET_PARENT(nbd) \
+ ((nbd)->mux_ops->get_parent((nbd)->cpu_clk))
+#define MESON_A1_CPU_CLK_SET_PARENT(nbd, index) \
+ ((nbd)->mux_ops->set_parent((nbd)->cpu_clk, index))
+
+static int meson_a1_cpu_clk_notifier_cb(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct a1_cpu_clk_nb_data *nbd;
+ int ret = 0;
+
+ nbd = container_of(nb, struct a1_cpu_clk_nb_data, nb);
+
+ switch (event) {
+ case PRE_RATE_CHANGE:
+ nbd->parent = MESON_A1_CPU_CLK_GET_PARENT(nbd);
+ /* Fallback to the CPU fixed clock */
+ ret = MESON_A1_CPU_CLK_SET_PARENT(nbd, 0);
+ /* Wait for clock propagation */
+ udelay(100);
+ break;
+
+ case POST_RATE_CHANGE:
+ case ABORT_RATE_CHANGE:
+ /* Back to the original parent clock */
+ ret = MESON_A1_CPU_CLK_SET_PARENT(nbd, nbd->parent);
+ /* Wait for clock propagation */
+ udelay(100);
+ break;
+
+ default:
+ pr_warn("Unknown event %lu for %s notifier block\n",
+ event, clk_hw_get_name(nbd->cpu_clk));
+ break;
+ }
+
+ return notifier_from_errno(ret);
+}
+
+static struct a1_cpu_clk_nb_data a1_cpu_clk_nb_data = {
+ .mux_ops = &clk_regmap_mux_ops,
+ .cpu_clk = &cpu_clk.hw,
+ .nb.notifier_call = meson_a1_cpu_clk_notifier_cb,
+};
+
+static int meson_a1_dvfs_setup(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct clk *notifier_clk;
+ int ret;
+
+ /* Setup clock notifier for cpu_clk */
+ notifier_clk = devm_clk_hw_get_clk(dev, &cpu_clk.hw, "dvfs");
+ if (IS_ERR(notifier_clk))
+ return dev_err_probe(dev, PTR_ERR(notifier_clk),
+ "can't get cpu_clk as notifier clock\n");
+
+ ret = devm_clk_notifier_register(dev, notifier_clk,
+ &a1_cpu_clk_nb_data.nb);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "can't register cpu_clk notifier\n");
+
+ return ret;
+}
+
+static int meson_a1_cpu_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ void __iomem *base;
+ struct regmap *map;
+ int clkid, i, err;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return dev_err_probe(dev, PTR_ERR(base),
+ "can't ioremap resource\n");
+
+ map = devm_regmap_init_mmio(dev, base, &a1_cpu_regmap_cfg);
+ if (IS_ERR(map))
+ return dev_err_probe(dev, PTR_ERR(map),
+ "can't init regmap mmio region\n");
+
+ /* Populate regmap for the regmap backed clocks */
+ for (i = 0; i < ARRAY_SIZE(a1_cpu_regmaps); i++)
+ a1_cpu_regmaps[i]->map = map;
+
+ for (clkid = 0; clkid < a1_cpu_clks.num; clkid++) {
+ err = devm_clk_hw_register(dev, a1_cpu_clks.hws[clkid]);
+ if (err)
+ return dev_err_probe(dev, err,
+ "clock[%d] registration failed\n",
+ clkid);
+ }
+
+ err = devm_of_clk_add_hw_provider(dev, meson_clk_hw_get, &a1_cpu_clks);
+ if (err)
+ return dev_err_probe(dev, err, "can't add clk hw provider\n");
+
+ return meson_a1_dvfs_setup(pdev);
+}
+
+static const struct of_device_id a1_cpu_clkc_match_table[] = {
+ { .compatible = "amlogic,a1-cpu-clkc", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, a1_cpu_clkc_match_table);
+
+static struct platform_driver a1_cpu_clkc_driver = {
+ .probe = meson_a1_cpu_probe,
+ .driver = {
+ .name = "a1-cpu-clkc",
+ .of_match_table = a1_cpu_clkc_match_table,
+ },
+};
+
+module_platform_driver(a1_cpu_clkc_driver);
+MODULE_AUTHOR("Dmitry Rokosov <ddrokosov@salutedevices.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/meson/a1-cpu.h b/drivers/clk/meson/a1-cpu.h
new file mode 100644
index 000000000000..e9af4117e26f
--- /dev/null
+++ b/drivers/clk/meson/a1-cpu.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Amlogic A1 CPU Clock Controller internals
+ *
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ * Author: Dmitry Rokosov <ddrokosov@salutedevices.com>
+ */
+
+#ifndef __A1_CPU_H
+#define __A1_CPU_H
+
+/* cpu clock controller register offset */
+#define CPUCTRL_CLK_CTRL0 0x80
+#define CPUCTRL_CLK_CTRL1 0x84
+
+#endif /* __A1_CPU_H */
--
2.43.0
^ permalink raw reply related
* [PATCH v1 0/4] arm64: dts: amlogic: a1: Support CPU Power Management
From: Dmitry Rokosov @ 2024-03-29 21:04 UTC (permalink / raw)
To: neil.armstrong, jbrunet, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl
Cc: kernel, rockosov, linux-amlogic, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, Dmitry Rokosov
The Amlogic A1 SoC family utilizes static operating points and a
PWM-controlled core voltage regulator that is specific to the board. As
the main CPU clock input, the SoC uses CLKID_CPU_CLK from the CPU clock
controller, which can be inherited from the system PLL (syspll) or a
fixed CPU clock.
Currently, the stable operating points at all frequencies are set to
800mV. This value is obtained from the vendor setup of several A1
boards.
The current patch series includes:
* CPU clock controller declaration
* syspll setup in the PLL controller
* operating points
* CPU special power parameters: voltage-tolerance, clock-latency,
capacity-dmips-mhz, dynamic-power-coefficient
Please be informed that the AD402 vddcpu PWM regulator does not exist in
this patch series because currently PWM A1 support is under development.
However, it should look like:
```
vddcpu: regulator-vddcpu {
compatible = "pwm-regulator";
pinctrl-0 = <&pwm_f_pins4>;
pinctrl-names = "default";
regulator-name = "VDDCPU";
regulator-min-microvolt = <690000>;
regulator-max-microvolt = <1050000>;
pwm-supply = <&dc_12v_in>;
pwms = <&pwm_ef 1 1500 0>; // 667kHz
voltage-table = <1050000 0>,
<1040000 3>,
<1030000 6>,
<1020000 8>,
<1010000 11>,
<1000000 14>,
<990000 17>,
<980000 20>,
<970000 23>,
<960000 26>,
<950000 29>,
<940000 31>,
<930000 34>,
<920000 37>,
<910000 40>,
<900000 43>,
<890000 45>,
<880000 48>,
<870000 51>,
<860000 54>,
<850000 56>,
<840000 59>,
<830000 62>,
<820000 65>,
<810000 68>,
<800000 70>,
<790000 73>,
<780000 76>,
<770000 79>,
<760000 81>,
<750000 84>,
<740000 87>,
<730000 89>,
<720000 92>,
<710000 95>,
<700000 98>,
<690000 100>;
regulator-boot-on;
regulator-always-on;
};
```
This patch series depends on [1].
Links:
[1] https://lore.kernel.org/all/20240329205904.25002-1-ddrokosov@salutedevices.com/
Dmitry Rokosov (4):
arm64: dts: amlogic: a1: add new syspll_in input for clkc_pll
controller
arm64: dts: amlogic: a1: declare cpu clock controller
arm64: dts: amlogic: a1: add new input clock 'sys_pll_div16' to
clkc_periphs
arm64: dts: amlogic: a1: setup CPU power management
arch/arm64/boot/dts/amlogic/meson-a1.dtsi | 68 ++++++++++++++++++++++-
1 file changed, 65 insertions(+), 3 deletions(-)
--
2.43.0
^ permalink raw reply
* [PATCH v1 1/4] arm64: dts: amlogic: a1: add new syspll_in input for clkc_pll controller
From: Dmitry Rokosov @ 2024-03-29 21:04 UTC (permalink / raw)
To: neil.armstrong, jbrunet, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl
Cc: kernel, rockosov, linux-amlogic, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, Dmitry Rokosov
In-Reply-To: <20240329210453.27530-1-ddrokosov@salutedevices.com>
Input clock 'syspll_in' is needed for the 'sys_pll' clock and is
inherited from the Peripherals Clock Controller.
Signed-off-by: Dmitry Rokosov <ddrokosov@salutedevices.com>
---
arch/arm64/boot/dts/amlogic/meson-a1.dtsi | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/amlogic/meson-a1.dtsi b/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
index 6f0d0e07e037..8ae944bfeee4 100644
--- a/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
@@ -895,8 +895,10 @@ clkc_pll: pll-clock-controller@7c80 {
reg = <0 0x7c80 0 0x18c>;
#clock-cells = <1>;
clocks = <&clkc_periphs CLKID_FIXPLL_IN>,
- <&clkc_periphs CLKID_HIFIPLL_IN>;
- clock-names = "fixpll_in", "hifipll_in";
+ <&clkc_periphs CLKID_HIFIPLL_IN>,
+ <&clkc_periphs CLKID_SYSPLL_IN>;
+ clock-names = "fixpll_in", "hifipll_in",
+ "syspll_in";
};
sd_emmc: sd@10000 {
--
2.43.0
^ permalink raw reply related
* [PATCH v1 2/4] arm64: dts: amlogic: a1: declare cpu clock controller
From: Dmitry Rokosov @ 2024-03-29 21:04 UTC (permalink / raw)
To: neil.armstrong, jbrunet, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl
Cc: kernel, rockosov, linux-amlogic, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, Dmitry Rokosov
In-Reply-To: <20240329210453.27530-1-ddrokosov@salutedevices.com>
The Amlogic A1 SoC family relies on the CPU clock controller to generate
CPU clocks, serving a crucial function. It has 4 inputs: main
oscillator, fixed clocks and system pll.
Signed-off-by: Dmitry Rokosov <ddrokosov@salutedevices.com>
---
arch/arm64/boot/dts/amlogic/meson-a1.dtsi | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/arch/arm64/boot/dts/amlogic/meson-a1.dtsi b/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
index 8ae944bfeee4..07fd0be828d4 100644
--- a/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
@@ -3,6 +3,7 @@
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
+#include <dt-bindings/clock/amlogic,a1-cpu-clkc.h>
#include <dt-bindings/clock/amlogic,a1-pll-clkc.h>
#include <dt-bindings/clock/amlogic,a1-peripherals-clkc.h>
#include <dt-bindings/clock/amlogic,a1-audio-clkc.h>
@@ -94,6 +95,19 @@ soc {
#size-cells = <2>;
ranges;
+ clkc_cpu: clock-controller@fd000000 {
+ compatible = "amlogic,a1-cpu-clkc";
+ reg = <0 0xfd000000 0 0x88>;
+ #clock-cells = <1>;
+
+ clocks = <&clkc_pll CLKID_FCLK_DIV2>,
+ <&clkc_pll CLKID_FCLK_DIV3>,
+ <&clkc_pll CLKID_SYS_PLL>,
+ <&xtal>;
+ clock-names = "fclk_div2", "fclk_div3",
+ "sys_pll", "xtal";
+ };
+
spifc: spi@fd000400 {
compatible = "amlogic,a1-spifc";
reg = <0x0 0xfd000400 0x0 0x290>;
--
2.43.0
^ permalink raw reply related
* [PATCH v1 3/4] arm64: dts: amlogic: a1: add new input clock 'sys_pll_div16' to clkc_periphs
From: Dmitry Rokosov @ 2024-03-29 21:04 UTC (permalink / raw)
To: neil.armstrong, jbrunet, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl
Cc: kernel, rockosov, linux-amlogic, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, Dmitry Rokosov
In-Reply-To: <20240329210453.27530-1-ddrokosov@salutedevices.com>
The input clock 'sys_pll_div16' is a clock with a fixed ratio inherited
from the main system PLL.
Signed-off-by: Dmitry Rokosov <ddrokosov@salutedevices.com>
---
arch/arm64/boot/dts/amlogic/meson-a1.dtsi | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/amlogic/meson-a1.dtsi b/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
index 07fd0be828d4..0de809f4d42c 100644
--- a/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
@@ -747,10 +747,12 @@ clkc_periphs: clock-controller@800 {
<&clkc_pll CLKID_FCLK_DIV5>,
<&clkc_pll CLKID_FCLK_DIV7>,
<&clkc_pll CLKID_HIFI_PLL>,
+ <&clkc_pll CLKID_SYS_PLL_DIV16>,
<&xtal>;
clock-names = "fclk_div2", "fclk_div3",
"fclk_div5", "fclk_div7",
- "hifi_pll", "xtal";
+ "hifi_pll", "sys_pll_div16",
+ "xtal";
};
i2c0: i2c@1400 {
--
2.43.0
^ permalink raw reply related
* [PATCH v1 4/4] arm64: dts: amlogic: a1: setup CPU power management
From: Dmitry Rokosov @ 2024-03-29 21:04 UTC (permalink / raw)
To: neil.armstrong, jbrunet, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl
Cc: kernel, rockosov, linux-amlogic, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, Dmitry Rokosov
In-Reply-To: <20240329210453.27530-1-ddrokosov@salutedevices.com>
The Amlogic A1 SoC family utilizes static operating points and a
PWM-controlled core voltage regulator, which is specific to the board.
As the main CPU clock input, the SoC uses CLKID_CPU_CLK from the CPU
clock controller, which can be inherited from the system PLL (syspll) or
a fixed CPU clock.
Currently, the stable operating points at all frequencies are set to
800mV. This value is obtained from the vendor setup of several A1
boards.
Signed-off-by: Dmitry Rokosov <ddrokosov@salutedevices.com>
---
arch/arm64/boot/dts/amlogic/meson-a1.dtsi | 44 +++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/arch/arm64/boot/dts/amlogic/meson-a1.dtsi b/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
index 0de809f4d42c..c57c7c1cd5f8 100644
--- a/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
@@ -33,6 +33,13 @@ cpu0: cpu@0 {
reg = <0x0 0x0>;
enable-method = "psci";
next-level-cache = <&l2>;
+ clocks = <&clkc_cpu CLKID_CPU_CLK>;
+ clock-names = "core_clk";
+ operating-points-v2 = <&cpu_opp_table0>;
+ voltage-tolerance = <0>;
+ clock-latency = <50000>;
+ capacity-dmips-mhz = <400>;
+ dynamic-power-coefficient = <80>;
#cooling-cells = <2>;
};
@@ -42,6 +49,13 @@ cpu1: cpu@1 {
reg = <0x0 0x1>;
enable-method = "psci";
next-level-cache = <&l2>;
+ clocks = <&clkc_cpu CLKID_CPU_CLK>;
+ clock-names = "core_clk";
+ operating-points-v2 = <&cpu_opp_table0>;
+ voltage-tolerance = <0>;
+ clock-latency = <50000>;
+ capacity-dmips-mhz = <400>;
+ dynamic-power-coefficient = <80>;
#cooling-cells = <2>;
};
@@ -52,6 +66,36 @@ l2: l2-cache0 {
};
};
+ cpu_opp_table0: cpu_opp_table0 {
+ compatible = "operating-points-v2";
+ opp-shared;
+
+ opp00 {
+ opp-hz = /bits/ 64 <128000000>;
+ opp-microvolt = <800000>;
+ };
+ opp01 {
+ opp-hz = /bits/ 64 <256000000>;
+ opp-microvolt = <800000>;
+ };
+ opp02 {
+ opp-hz = /bits/ 64 <512000000>;
+ opp-microvolt = <800000>;
+ };
+ opp03 {
+ opp-hz = /bits/ 64 <768000000>;
+ opp-microvolt = <800000>;
+ };
+ opp04 {
+ opp-hz = /bits/ 64 <1008000000>;
+ opp-microvolt = <800000>;
+ };
+ opp05 {
+ opp-hz = /bits/ 64 <1200000000>;
+ opp-microvolt = <800000>;
+ };
+ };
+
efuse: efuse {
compatible = "amlogic,meson-gxbb-efuse";
clocks = <&clkc_periphs CLKID_OTP>;
--
2.43.0
^ permalink raw reply related
* [PATCH v7 6/7] iommu/arm-smmu-qcom: Use the custom fault handler on more platforms
From: Georgi Djakov @ 2024-03-29 21:06 UTC (permalink / raw)
To: will, robin.murphy, joro, iommu
Cc: robh+dt, krzysztof.kozlowski+dt, conor+dt, devicetree, andersson,
konrad.dybcio, robdclark, linux-arm-kernel, linux-kernel,
linux-arm-msm, quic_cgoldswo, quic_sukadev, quic_pdaly,
quic_sudaraja, djakov
In-Reply-To: <20240329210638.3647523-1-quic_c_gdjako@quicinc.com>
The TBU support is now available, so let's allow it to be used on other
platforms that have the Qualcomm SMMU-500 implementation with TBUs. This
will allow the context fault handler to query the TBUs when a context
fault occurs.
Signed-off-by: Georgi Djakov <quic_c_gdjako@quicinc.com>
---
drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c
index 7a58b1b96bca..08e0ad325bab 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c
@@ -413,6 +413,10 @@ static const struct arm_smmu_impl qcom_smmu_500_impl = {
.reset = arm_mmu500_reset,
.write_s2cr = qcom_smmu_write_s2cr,
.tlb_sync = qcom_smmu_tlb_sync,
+#ifdef CONFIG_ARM_SMMU_QCOM_TBU
+ .context_fault = qcom_smmu_context_fault,
+ .context_fault_needs_threaded_irq = true,
+#endif
};
static const struct arm_smmu_impl sdm845_smmu_500_impl = {
^ permalink raw reply related
* [PATCH v7 0/7] Add support for Translation Buffer Units
From: Georgi Djakov @ 2024-03-29 21:06 UTC (permalink / raw)
To: will, robin.murphy, joro, iommu
Cc: robh+dt, krzysztof.kozlowski+dt, conor+dt, devicetree, andersson,
konrad.dybcio, robdclark, linux-arm-kernel, linux-kernel,
linux-arm-msm, quic_cgoldswo, quic_sukadev, quic_pdaly,
quic_sudaraja, djakov
The TCUs (Translation Control Units) and TBUs (Translation Buffer
Units) are key components of the MMU-500. Multiple TBUs are connected
to a single TCU over an interconnect. Each TBU contains a TLB that
caches page tables. The MMU-500 implements a TBU for each connected
master, and the TBU is designed, so that it is local to the master.
A common TBU DT schema is added to describe the TBUs.
The Qualcomm SDM845 and SC7280 platforms have an implementation of the
SMMU-500, that has multiple TBUs. A vendor-specific DT schema is added
to describe the resources for each TBU (register space, power-domains,
interconnects and clocks).
The TBU driver will manage the resources and allow the system to
operate the TBUs during a context fault to obtain details by doing
s1 inv, software + hardware page table walks etc. This is implemented
with ATOS/eCATs as the ATS feature is not supported. Being able to
query the TBUs is useful for debugging various hardware/software
issues on these platforms.
v7:
- Pick Reviewed-by for the DT binding (Rob)
- Don't spam the log if the dts changes are not there (Konrad)
v6: https://lore.kernel.org/r/20240307190525.395291-1-quic_c_gdjako@quicinc.com/
- Use SoC-specific compatibles (Krzysztof)
- Use additionalProperties: false (Krzysztof)
- Wrap description text to 80 cols (Krzysztof)
v5: https://lore.kernel.org/r/20240226172218.69486-1-quic_c_gdjako@quicinc.com
- Drop the common TBU bindings and child nodes. These TBU functionalities
are only Qualcomm specific and not generic. In the unmodified ARM MMU-500
implementation there are no TBU-specific resources, so just make them
standalone DT nodes. (Robin)
- The "qcom,stream-id-range" DT property now takes a phandle to the smmu
and a stream ID range.
v4: https://lore.kernel.org/r/20240201210529.7728-1-quic_c_gdjako@quicinc.com/
- Create a common TBU schema. Move the vendor-specific properties into
a separate schema that references the common one. (Rob)
- Drop unused DT labels in example, fix regex. (Rob)
- Properly rebase on latest code.
v3: https://lore.kernel.org/r/20231220060236.18600-1-quic_c_gdjako@quicinc.com
- Having a TBU is not Qualcomm specific, so allow having TBU child
nodes with no specific constraints on properties. For some of the
vendor compatibles however, add a schema to describe specific
properties and allow validation. (Rob)
- Drop the useless reg-names DT property on TBUs. (Rob)
- Make the stream-id-range DT property a common one. (Rob)
- Fix the DT example. (Rob)
- Minor fixes on the TBU driver.
- Add support for SC7280 platforms.
v2: https://lore.kernel.org/r/20231118042730.2799-1-quic_c_gdjako@quicinc.com
- Improve DT binding description, add full example. (Konrad)
- Drop Qcom specific stuff from the generic binding. (Rob)
- Unconditionally try to populate subnodes. (Konrad)
- Improve TBU driver commit text, remove memory barriers. (Bjorn)
- Move TBU stuff into separate file. Make the driver builtin.
- TODO: Evaluate whether to keep TBU support as a separate driver
or just instantiate things from qcom_smmu_impl_init()
v1: https://lore.kernel.org/r/20231019021923.13939-1-quic_c_gdjako@quicinc.com
Georgi Djakov (7):
dt-bindings: iommu: Add Qualcomm TBU
iommu/arm-smmu-qcom-tbu: Add Qualcomm TBU driver
iommu/arm-smmu: Allow using a threaded handler for context interrupts
iommu/arm-smmu-qcom: Use a custom context fault handler for sdm845
arm64: dts: qcom: sdm845: Add DT nodes for the TBUs
iommu/arm-smmu-qcom: Use the custom fault handler on more platforms
arm64: dts: qcom: sc7280: Add DT nodes for the TBUs
.../devicetree/bindings/iommu/qcom,tbu.yaml | 69 +++
arch/arm64/boot/dts/qcom/sc7280.dtsi | 89 +++
arch/arm64/boot/dts/qcom/sdm845.dtsi | 70 +++
drivers/iommu/Kconfig | 9 +
drivers/iommu/arm/arm-smmu/Makefile | 1 +
.../iommu/arm/arm-smmu/arm-smmu-qcom-tbu.c | 515 ++++++++++++++++++
drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 8 +
drivers/iommu/arm/arm-smmu/arm-smmu-qcom.h | 2 +
drivers/iommu/arm/arm-smmu/arm-smmu.c | 12 +-
drivers/iommu/arm/arm-smmu/arm-smmu.h | 3 +
10 files changed, 776 insertions(+), 2 deletions(-)
create mode 100644 Documentation/devicetree/bindings/iommu/qcom,tbu.yaml
create mode 100644 drivers/iommu/arm/arm-smmu/arm-smmu-qcom-tbu.c
^ permalink raw reply
* [PATCH v7 5/7] arm64: dts: qcom: sdm845: Add DT nodes for the TBUs
From: Georgi Djakov @ 2024-03-29 21:06 UTC (permalink / raw)
To: will, robin.murphy, joro, iommu
Cc: robh+dt, krzysztof.kozlowski+dt, conor+dt, devicetree, andersson,
konrad.dybcio, robdclark, linux-arm-kernel, linux-kernel,
linux-arm-msm, quic_cgoldswo, quic_sukadev, quic_pdaly,
quic_sudaraja, djakov
In-Reply-To: <20240329210638.3647523-1-quic_c_gdjako@quicinc.com>
Add the device-tree nodes for the TBUs (translation buffer units) that
are present on the sdm845 platforms. The TBUs can be used debug the
kernel and provide additional information when a context faults occur.
Describe the all registers, clocks, interconnects and power-domain
resources that are needed for each of the TBUs.
Signed-off-by: Georgi Djakov <quic_c_gdjako@quicinc.com>
---
arch/arm64/boot/dts/qcom/sdm845.dtsi | 70 ++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/sdm845.dtsi b/arch/arm64/boot/dts/qcom/sdm845.dtsi
index 2f20be99ee7e..381537f03fae 100644
--- a/arch/arm64/boot/dts/qcom/sdm845.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi
@@ -15,6 +15,7 @@
#include <dt-bindings/dma/qcom-gpi.h>
#include <dt-bindings/firmware/qcom,scm.h>
#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interconnect/qcom,icc.h>
#include <dt-bindings/interconnect/qcom,osm-l3.h>
#include <dt-bindings/interconnect/qcom,sdm845.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
@@ -5085,6 +5086,75 @@ apps_smmu: iommu@15000000 {
<GIC_SPI 343 IRQ_TYPE_LEVEL_HIGH>;
};
+ anoc_1_tbu: tbu@150c5000 {
+ compatible = "qcom,sdm845-tbu";
+ reg = <0x0 0x150c5000 0x0 0x1000>;
+ interconnects = <&system_noc MASTER_GNOC_SNOC QCOM_ICC_TAG_ACTIVE_ONLY
+ &config_noc SLAVE_IMEM_CFG QCOM_ICC_TAG_ACTIVE_ONLY>;
+ power-domains = <&gcc HLOS1_VOTE_AGGRE_NOC_MMU_TBU1_GDSC>;
+ qcom,stream-id-range = <&apps_smmu 0x0 0x400>;
+ };
+
+ anoc_2_tbu: tbu@150c9000 {
+ compatible = "qcom,sdm845-tbu";
+ reg = <0x0 0x150c9000 0x0 0x1000>;
+ interconnects = <&system_noc MASTER_GNOC_SNOC QCOM_ICC_TAG_ACTIVE_ONLY
+ &config_noc SLAVE_IMEM_CFG QCOM_ICC_TAG_ACTIVE_ONLY>;
+ power-domains = <&gcc HLOS1_VOTE_AGGRE_NOC_MMU_TBU2_GDSC>;
+ qcom,stream-id-range = <&apps_smmu 0x400 0x400>;
+ };
+
+ mnoc_hf_0_tbu: tbu@150cd000 {
+ compatible = "qcom,sdm845-tbu";
+ reg = <0x0 0x150cd000 0x0 0x1000>;
+ interconnects = <&mmss_noc MASTER_MDP0 QCOM_ICC_TAG_ACTIVE_ONLY
+ &mmss_noc SLAVE_MNOC_HF_MEM_NOC QCOM_ICC_TAG_ACTIVE_ONLY>;
+ qcom,stream-id-range = <&apps_smmu 0x800 0x400>;
+ };
+
+ mnoc_hf_1_tbu: tbu@150d1000 {
+ compatible = "qcom,sdm845-tbu";
+ reg = <0x0 0x150d1000 0x0 0x1000>;
+ interconnects = <&mmss_noc MASTER_MDP0 QCOM_ICC_TAG_ACTIVE_ONLY
+ &mmss_noc SLAVE_MNOC_HF_MEM_NOC QCOM_ICC_TAG_ACTIVE_ONLY>;
+ qcom,stream-id-range = <&apps_smmu 0xc00 0x400>;
+ };
+
+ mnoc_sf_0_tbu: tbu@150d5000 {
+ compatible = "qcom,sdm845-tbu";
+ reg = <0x0 0x150d5000 0x0 0x1000>;
+ interconnects = <&mmss_noc MASTER_CAMNOC_SF QCOM_ICC_TAG_ACTIVE_ONLY
+ &mmss_noc SLAVE_MNOC_SF_MEM_NOC QCOM_ICC_TAG_ACTIVE_ONLY>;
+ qcom,stream-id-range = <&apps_smmu 0x1000 0x400>;
+ };
+
+ compute_dsp_tbu: tbu@150d9000 {
+ compatible = "qcom,sdm845-tbu";
+ reg = <0x0 0x150d9000 0x0 0x1000>;
+ interconnects = <&system_noc MASTER_GNOC_SNOC QCOM_ICC_TAG_ACTIVE_ONLY
+ &config_noc SLAVE_IMEM_CFG QCOM_ICC_TAG_ACTIVE_ONLY>;
+ qcom,stream-id-range = <&apps_smmu 0x1400 0x400>;
+ };
+
+ adsp_tbu: tbu@150dd000 {
+ compatible = "qcom,sdm845-tbu";
+ reg = <0x0 0x150dd000 0x0 0x1000>;
+ interconnects = <&system_noc MASTER_GNOC_SNOC QCOM_ICC_TAG_ACTIVE_ONLY
+ &config_noc SLAVE_IMEM_CFG QCOM_ICC_TAG_ACTIVE_ONLY>;
+ power-domains = <&gcc HLOS1_VOTE_AGGRE_NOC_MMU_AUDIO_TBU_GDSC>;
+ qcom,stream-id-range = <&apps_smmu 0x1800 0x400>;
+ };
+
+ anoc_1_pcie_tbu: tbu@150e1000 {
+ compatible = "qcom,sdm845-tbu";
+ reg = <0x0 0x150e1000 0x0 0x1000>;
+ clocks = <&gcc GCC_AGGRE_NOC_PCIE_TBU_CLK>;
+ interconnects = <&system_noc MASTER_GNOC_SNOC QCOM_ICC_TAG_ACTIVE_ONLY
+ &config_noc SLAVE_IMEM_CFG QCOM_ICC_TAG_ACTIVE_ONLY>;
+ power-domains = <&gcc HLOS1_VOTE_AGGRE_NOC_MMU_PCIE_TBU_GDSC>;
+ qcom,stream-id-range = <&apps_smmu 0x1c00 0x400>;
+ };
+
lpasscc: clock-controller@17014000 {
compatible = "qcom,sdm845-lpasscc";
reg = <0 0x17014000 0 0x1f004>, <0 0x17300000 0 0x200>;
^ permalink raw reply related
* [PATCH v7 4/7] iommu/arm-smmu-qcom: Use a custom context fault handler for sdm845
From: Georgi Djakov @ 2024-03-29 21:06 UTC (permalink / raw)
To: will, robin.murphy, joro, iommu
Cc: robh+dt, krzysztof.kozlowski+dt, conor+dt, devicetree, andersson,
konrad.dybcio, robdclark, linux-arm-kernel, linux-kernel,
linux-arm-msm, quic_cgoldswo, quic_sukadev, quic_pdaly,
quic_sudaraja, djakov
In-Reply-To: <20240329210638.3647523-1-quic_c_gdjako@quicinc.com>
The sdm845 platform now supports TBUs, so let's get additional debug
info from the TBUs when a context fault occurs. Implement a custom
context fault handler that does both software + hardware page table
walks and TLB Invalidate All.
Signed-off-by: Georgi Djakov <quic_c_gdjako@quicinc.com>
---
.../iommu/arm/arm-smmu/arm-smmu-qcom-tbu.c | 143 ++++++++++++++++++
drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 4 +
2 files changed, 147 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-tbu.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-tbu.c
index e3202ed89566..7c7f02618a08 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-tbu.c
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-tbu.c
@@ -312,6 +312,149 @@ static phys_addr_t qcom_iova_to_phys(struct arm_smmu_domain *smmu_domain,
return phys;
}
+static phys_addr_t qcom_smmu_iova_to_phys_hard(struct arm_smmu_domain *smmu_domain, dma_addr_t iova)
+{
+ struct arm_smmu_device *smmu = smmu_domain->smmu;
+ int idx = smmu_domain->cfg.cbndx;
+ u32 frsynra;
+ u16 sid;
+
+ frsynra = arm_smmu_gr1_read(smmu, ARM_SMMU_GR1_CBFRSYNRA(idx));
+ sid = FIELD_GET(ARM_SMMU_CBFRSYNRA_SID, frsynra);
+
+ return qcom_iova_to_phys(smmu_domain, iova, sid);
+}
+
+static phys_addr_t qcom_smmu_verify_fault(struct arm_smmu_domain *smmu_domain, dma_addr_t iova, u32 fsr)
+{
+ struct io_pgtable *iop = io_pgtable_ops_to_pgtable(smmu_domain->pgtbl_ops);
+ struct arm_smmu_device *smmu = smmu_domain->smmu;
+ phys_addr_t phys_post_tlbiall;
+ phys_addr_t phys;
+
+ phys = qcom_smmu_iova_to_phys_hard(smmu_domain, iova);
+ io_pgtable_tlb_flush_all(iop);
+ phys_post_tlbiall = qcom_smmu_iova_to_phys_hard(smmu_domain, iova);
+
+ if (phys != phys_post_tlbiall) {
+ dev_err(smmu->dev,
+ "ATOS results differed across TLBIALL... (before: %pa after: %pa)\n",
+ &phys, &phys_post_tlbiall);
+ }
+
+ return (phys == 0 ? phys_post_tlbiall : phys);
+}
+
+irqreturn_t qcom_smmu_context_fault(int irq, void *dev)
+{
+ struct arm_smmu_domain *smmu_domain = dev;
+ struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops;
+ struct arm_smmu_device *smmu = smmu_domain->smmu;
+ u32 fsr, fsynr, cbfrsynra, resume = 0;
+ int idx = smmu_domain->cfg.cbndx;
+ phys_addr_t phys_soft;
+ unsigned long iova;
+ int ret, tmp;
+
+ static DEFINE_RATELIMIT_STATE(_rs,
+ DEFAULT_RATELIMIT_INTERVAL,
+ DEFAULT_RATELIMIT_BURST);
+
+ fsr = arm_smmu_cb_read(smmu, idx, ARM_SMMU_CB_FSR);
+ if (!(fsr & ARM_SMMU_FSR_FAULT))
+ return IRQ_NONE;
+
+ fsynr = arm_smmu_cb_read(smmu, idx, ARM_SMMU_CB_FSYNR0);
+ iova = arm_smmu_cb_readq(smmu, idx, ARM_SMMU_CB_FAR);
+ cbfrsynra = arm_smmu_gr1_read(smmu, ARM_SMMU_GR1_CBFRSYNRA(idx));
+
+ if (list_empty(&tbu_list)) {
+ ret = report_iommu_fault(&smmu_domain->domain, NULL, iova,
+ fsynr & ARM_SMMU_FSYNR0_WNR ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ);
+
+ if (ret == -ENOSYS)
+ dev_err_ratelimited(smmu->dev,
+ "Unhandled context fault: fsr=0x%x, iova=0x%08lx, fsynr=0x%x, cbfrsynra=0x%x, cb=%d\n",
+ fsr, iova, fsynr, cbfrsynra, idx);
+
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_FSR, fsr);
+ return IRQ_HANDLED;
+ }
+
+ phys_soft = ops->iova_to_phys(ops, iova);
+
+ tmp = report_iommu_fault(&smmu_domain->domain, NULL, iova,
+ fsynr & ARM_SMMU_FSYNR0_WNR ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ);
+ if (!tmp || tmp == -EBUSY) {
+ dev_dbg(smmu->dev,
+ "Context fault handled by client: iova=0x%08lx, fsr=0x%x, fsynr=0x%x, cb=%d\n",
+ iova, fsr, fsynr, idx);
+ dev_dbg(smmu->dev, "soft iova-to-phys=%pa\n", &phys_soft);
+ ret = IRQ_HANDLED;
+ resume = ARM_SMMU_RESUME_TERMINATE;
+ } else {
+ phys_addr_t phys_atos = qcom_smmu_verify_fault(smmu_domain, iova, fsr);
+
+ if (__ratelimit(&_rs)) {
+ dev_err(smmu->dev,
+ "Unhandled context fault: fsr=0x%x, iova=0x%08lx, fsynr=0x%x, cbfrsynra=0x%x, cb=%d\n",
+ fsr, iova, fsynr, cbfrsynra, idx);
+ dev_err(smmu->dev,
+ "FSR = %08x [%s%s%s%s%s%s%s%s%s], SID=0x%x\n",
+ fsr,
+ (fsr & 0x02) ? "TF " : "",
+ (fsr & 0x04) ? "AFF " : "",
+ (fsr & 0x08) ? "PF " : "",
+ (fsr & 0x10) ? "EF " : "",
+ (fsr & 0x20) ? "TLBMCF " : "",
+ (fsr & 0x40) ? "TLBLKF " : "",
+ (fsr & 0x80) ? "MHF " : "",
+ (fsr & 0x40000000) ? "SS " : "",
+ (fsr & 0x80000000) ? "MULTI " : "",
+ cbfrsynra);
+
+ dev_err(smmu->dev,
+ "soft iova-to-phys=%pa\n", &phys_soft);
+ if (!phys_soft)
+ dev_err(smmu->dev,
+ "SOFTWARE TABLE WALK FAILED! Looks like %s accessed an unmapped address!\n",
+ dev_name(smmu->dev));
+ if (phys_atos)
+ dev_err(smmu->dev, "hard iova-to-phys (ATOS)=%pa\n",
+ &phys_atos);
+ else
+ dev_err(smmu->dev, "hard iova-to-phys (ATOS) failed\n");
+ }
+ ret = IRQ_NONE;
+ resume = ARM_SMMU_RESUME_TERMINATE;
+ }
+
+ /*
+ * If the client returns -EBUSY, do not clear FSR and do not RESUME
+ * if stalled. This is required to keep the IOMMU client stalled on
+ * the outstanding fault. This gives the client a chance to take any
+ * debug action and then terminate the stalled transaction.
+ * So, the sequence in case of stall on fault should be:
+ * 1) Do not clear FSR or write to RESUME here
+ * 2) Client takes any debug action
+ * 3) Client terminates the stalled transaction and resumes the IOMMU
+ * 4) Client clears FSR. The FSR should only be cleared after 3) and
+ * not before so that the fault remains outstanding. This ensures
+ * SCTLR.HUPCF has the desired effect if subsequent transactions also
+ * need to be terminated.
+ */
+ if (tmp != -EBUSY) {
+ /* Clear the faulting FSR */
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_FSR, fsr);
+
+ /* Retry or terminate any stalled transactions */
+ if (fsr & ARM_SMMU_FSR_SS)
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_RESUME, resume);
+ }
+
+ return ret;
+}
+
static int qcom_tbu_probe(struct platform_device *pdev)
{
struct of_phandle_args args = { .args_count = 2 };
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c
index 5c7cfc51b57c..7a58b1b96bca 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c
@@ -422,6 +422,10 @@ static const struct arm_smmu_impl sdm845_smmu_500_impl = {
.reset = qcom_sdm845_smmu500_reset,
.write_s2cr = qcom_smmu_write_s2cr,
.tlb_sync = qcom_smmu_tlb_sync,
+#ifdef CONFIG_ARM_SMMU_QCOM_TBU
+ .context_fault = qcom_smmu_context_fault,
+ .context_fault_needs_threaded_irq = true,
+#endif
};
static const struct arm_smmu_impl qcom_adreno_smmu_v2_impl = {
^ permalink raw reply related
* [PATCH v7 3/7] iommu/arm-smmu: Allow using a threaded handler for context interrupts
From: Georgi Djakov @ 2024-03-29 21:06 UTC (permalink / raw)
To: will, robin.murphy, joro, iommu
Cc: robh+dt, krzysztof.kozlowski+dt, conor+dt, devicetree, andersson,
konrad.dybcio, robdclark, linux-arm-kernel, linux-kernel,
linux-arm-msm, quic_cgoldswo, quic_sukadev, quic_pdaly,
quic_sudaraja, djakov
In-Reply-To: <20240329210638.3647523-1-quic_c_gdjako@quicinc.com>
Threaded IRQ handlers run in a less critical context compared to normal
IRQs, so they can perform more complex and time-consuming operations
without causing significant delays in other parts of the kernel.
During a context fault, it might be needed to do more processing and
gather debug information from TBUs in the handler. These operations may
sleep, so add an option to use a threaded IRQ handler in these cases.
Signed-off-by: Georgi Djakov <quic_c_gdjako@quicinc.com>
---
drivers/iommu/arm/arm-smmu/arm-smmu.c | 12 ++++++++++--
drivers/iommu/arm/arm-smmu/arm-smmu.h | 1 +
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c
index c572d877b0e1..dcf0479363c3 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu.c
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c
@@ -806,8 +806,16 @@ static int arm_smmu_init_domain_context(struct arm_smmu_domain *smmu_domain,
else
context_fault = arm_smmu_context_fault;
- ret = devm_request_irq(smmu->dev, irq, context_fault, IRQF_SHARED,
- "arm-smmu-context-fault", smmu_domain);
+ if (smmu->impl && smmu->impl->context_fault_needs_threaded_irq)
+ ret = devm_request_threaded_irq(smmu->dev, irq, NULL,
+ context_fault,
+ IRQF_ONESHOT | IRQF_SHARED,
+ "arm-smmu-context-fault",
+ smmu_domain);
+ else
+ ret = devm_request_irq(smmu->dev, irq, context_fault, IRQF_SHARED,
+ "arm-smmu-context-fault", smmu_domain);
+
if (ret < 0) {
dev_err(smmu->dev, "failed to request context IRQ %d (%u)\n",
cfg->irptndx, irq);
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.h b/drivers/iommu/arm/arm-smmu/arm-smmu.h
index 1670e95c4637..4765c6945c34 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu.h
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu.h
@@ -438,6 +438,7 @@ struct arm_smmu_impl {
int (*def_domain_type)(struct device *dev);
irqreturn_t (*global_fault)(int irq, void *dev);
irqreturn_t (*context_fault)(int irq, void *dev);
+ bool context_fault_needs_threaded_irq;
int (*alloc_context_bank)(struct arm_smmu_domain *smmu_domain,
struct arm_smmu_device *smmu,
struct device *dev, int start);
^ permalink raw reply related
* [PATCH v7 1/7] dt-bindings: iommu: Add Qualcomm TBU
From: Georgi Djakov @ 2024-03-29 21:06 UTC (permalink / raw)
To: will, robin.murphy, joro, iommu
Cc: robh+dt, krzysztof.kozlowski+dt, conor+dt, devicetree, andersson,
konrad.dybcio, robdclark, linux-arm-kernel, linux-kernel,
linux-arm-msm, quic_cgoldswo, quic_sukadev, quic_pdaly,
quic_sudaraja, djakov
In-Reply-To: <20240329210638.3647523-1-quic_c_gdjako@quicinc.com>
The "apps_smmu" on the Qualcomm sdm845 platform is an implementation
of the SMMU-500, that consists of a single TCU (Translation Control
Unit) and multiple TBUs (Translation Buffer Units). These TBUs have
hardware debugging features that are specific and only present on
Qualcomm hardware. Represent them as independent DT nodes. List all
the resources that are needed to operate them (such as registers,
clocks, power domains and interconnects).
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Georgi Djakov <quic_c_gdjako@quicinc.com>
---
.../devicetree/bindings/iommu/qcom,tbu.yaml | 69 +++++++++++++++++++
1 file changed, 69 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iommu/qcom,tbu.yaml
diff --git a/Documentation/devicetree/bindings/iommu/qcom,tbu.yaml b/Documentation/devicetree/bindings/iommu/qcom,tbu.yaml
new file mode 100644
index 000000000000..82dfe935573e
--- /dev/null
+++ b/Documentation/devicetree/bindings/iommu/qcom,tbu.yaml
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iommu/qcom,tbu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm TBU (Translation Buffer Unit)
+
+maintainers:
+ - Georgi Djakov <quic_c_gdjako@quicinc.com>
+
+description:
+ The Qualcomm SMMU500 implementation consists of TCU and TBU. The TBU contains
+ a Translation Lookaside Buffer (TLB) that caches page tables. TBUs provides
+ debug features to trace and trigger debug transactions. There are multiple TBU
+ instances with each client core.
+
+properties:
+ compatible:
+ enum:
+ - qcom,sc7280-tbu
+ - qcom,sdm845-tbu
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ interconnects:
+ maxItems: 1
+
+ power-domains:
+ maxItems: 1
+
+ qcom,stream-id-range:
+ description: |
+ Phandle of a SMMU device and Stream ID range (address and size) that
+ is assigned by the TBU
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ items:
+ - items:
+ - description: phandle of a smmu node
+ - description: stream id base address
+ - description: stream id size
+
+required:
+ - compatible
+ - reg
+ - qcom,stream-id-range
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/qcom,gcc-sdm845.h>
+ #include <dt-bindings/interconnect/qcom,icc.h>
+ #include <dt-bindings/interconnect/qcom,sdm845.h>
+
+ tbu@150e1000 {
+ compatible = "qcom,sdm845-tbu";
+ reg = <0x150e1000 0x1000>;
+ clocks = <&gcc GCC_AGGRE_NOC_PCIE_TBU_CLK>;
+ interconnects = <&system_noc MASTER_GNOC_SNOC QCOM_ICC_TAG_ACTIVE_ONLY
+ &config_noc SLAVE_IMEM_CFG QCOM_ICC_TAG_ACTIVE_ONLY>;
+ power-domains = <&gcc HLOS1_VOTE_AGGRE_NOC_MMU_PCIE_TBU_GDSC>;
+ qcom,stream-id-range = <&apps_smmu 0x1c00 0x400>;
+ };
+...
^ permalink raw reply related
* [PATCH v7 7/7] arm64: dts: qcom: sc7280: Add DT nodes for the TBUs
From: Georgi Djakov @ 2024-03-29 21:06 UTC (permalink / raw)
To: will, robin.murphy, joro, iommu
Cc: robh+dt, krzysztof.kozlowski+dt, conor+dt, devicetree, andersson,
konrad.dybcio, robdclark, linux-arm-kernel, linux-kernel,
linux-arm-msm, quic_cgoldswo, quic_sukadev, quic_pdaly,
quic_sudaraja, djakov
In-Reply-To: <20240329210638.3647523-1-quic_c_gdjako@quicinc.com>
Add the device-tree nodes for the TBUs (translation buffer units) that
are present on the sc7280 platforms. The TBUs can be used debug the
kernel and provide additional information when a context faults occur.
Describe all the registers, clocks, interconnects and power-domain
resources that are needed for each of the TBUs.
Signed-off-by: Georgi Djakov <quic_c_gdjako@quicinc.com>
---
arch/arm64/boot/dts/qcom/sc7280.dtsi | 89 ++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/sc7280.dtsi b/arch/arm64/boot/dts/qcom/sc7280.dtsi
index 7e7f0f0fb41b..5d8aa182e3a9 100644
--- a/arch/arm64/boot/dts/qcom/sc7280.dtsi
+++ b/arch/arm64/boot/dts/qcom/sc7280.dtsi
@@ -2970,6 +2970,18 @@ adreno_smmu: iommu@3da0000 {
dma-coherent;
};
+ gfx_0_tbu: tbu@3dd9000 {
+ compatible = "qcom,sc7280-tbu";
+ reg = <0x0 0x3dd9000 0x0 0x1000>;
+ qcom,stream-id-range = <&adreno_smmu 0x0 0x400>;
+ };
+
+ gfx_1_tbu: tbu@3ddd000 {
+ compatible = "qcom,sc7280-tbu";
+ reg = <0x0 0x3ddd000 0x0 0x1000>;
+ qcom,stream-id-range = <&adreno_smmu 0x400 0x400>;
+ };
+
remoteproc_mpss: remoteproc@4080000 {
compatible = "qcom,sc7280-mpss-pas";
reg = <0 0x04080000 0 0x10000>;
@@ -5778,6 +5790,83 @@ apps_smmu: iommu@15000000 {
<GIC_SPI 408 IRQ_TYPE_LEVEL_HIGH>;
};
+ anoc_1_tbu: tbu@151dd000 {
+ compatible = "qcom,sc7280-tbu";
+ reg = <0x0 0x151dd000 0x0 0x1000>;
+ interconnects = <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY
+ &cnoc3 SLAVE_TCU QCOM_ICC_TAG_ACTIVE_ONLY>;
+ qcom,stream-id-range = <&apps_smmu 0x0 0x400>;
+ };
+
+ anoc_2_tbu: tbu@151e1000 {
+ compatible = "qcom,sc7280-tbu";
+ reg = <0x0 0x151e1000 0x0 0x1000>;
+ interconnects = <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY
+ &cnoc3 SLAVE_TCU QCOM_ICC_TAG_ACTIVE_ONLY>;
+ qcom,stream-id-range = <&apps_smmu 0x400 0x400>;
+ };
+
+ mnoc_hf_0_tbu: tbu@151e5000 {
+ compatible = "qcom,sc7280-tbu";
+ reg = <0x0 0x151e5000 0x0 0x1000>;
+ interconnects = <&mmss_noc MASTER_MDP0 QCOM_ICC_TAG_ACTIVE_ONLY
+ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ACTIVE_ONLY>;
+ power-domains = <&gcc HLOS1_VOTE_MMNOC_MMU_TBU_HF0_GDSC>;
+ qcom,stream-id-range = <&apps_smmu 0x800 0x400>;
+ };
+
+ mnoc_hf_1_tbu: tbu@151e9000 {
+ compatible = "qcom,sc7280-tbu";
+ reg = <0x0 0x151e9000 0x0 0x1000>;
+ interconnects = <&mmss_noc MASTER_MDP0 QCOM_ICC_TAG_ACTIVE_ONLY
+ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ACTIVE_ONLY>;
+ power-domains = <&gcc HLOS1_VOTE_MMNOC_MMU_TBU_HF1_GDSC>;
+ qcom,stream-id-range = <&apps_smmu 0xc00 0x400>;
+ };
+
+ compute_dsp_0_tbu: tbu@151ed000 {
+ compatible = "qcom,sc7280-tbu";
+ reg = <0x0 0x151ed000 0x0 0x1000>;
+ interconnects = <&nsp_noc MASTER_CDSP_PROC QCOM_ICC_TAG_ACTIVE_ONLY
+ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ACTIVE_ONLY>;
+ power-domains = <&gcc HLOS1_VOTE_TURING_MMU_TBU1_GDSC>;
+ qcom,stream-id-range = <&apps_smmu 0x1000 0x400>;
+ };
+
+ compute_dsp_1_tbu: tbu@151f1000 {
+ compatible = "qcom,sc7280-tbu";
+ reg = <0x0 0x151f1000 0x0 0x1000>;
+ interconnects = <&nsp_noc MASTER_CDSP_PROC QCOM_ICC_TAG_ACTIVE_ONLY
+ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ACTIVE_ONLY>;
+ power-domains = <&gcc HLOS1_VOTE_TURING_MMU_TBU0_GDSC>;
+ qcom,stream-id-range = <&apps_smmu 0x1400 0x400>;
+ };
+
+ adsp_tbu: tbu@151f5000 {
+ compatible = "qcom,sc7280-tbu";
+ reg = <0x0 0x151f5000 0x0 0x1000>;
+ interconnects = <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY
+ &lpass_ag_noc SLAVE_LPASS_CORE_CFG QCOM_ICC_TAG_ACTIVE_ONLY>;
+ qcom,stream-id-range = <&apps_smmu 0x1800 0x400>;
+ };
+
+ anoc_1_pcie_tbu: tbu@151f9000 {
+ compatible = "qcom,sc7280-tbu";
+ reg = <0x0 0x151f9000 0x0 0x1000>;
+ interconnects = <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY
+ &cnoc3 SLAVE_TCU QCOM_ICC_TAG_ACTIVE_ONLY>;
+ qcom,stream-id-range = <&apps_smmu 0x1c00 0x400>;
+ };
+
+ mnoc_sf_0_tbu: tbu@151fd000 {
+ compatible = "qcom,sc7280-tbu";
+ reg = <0x0 0x151fd000 0x0 0x1000>;
+ interconnects = <&mmss_noc MASTER_CAMNOC_SF QCOM_ICC_TAG_ACTIVE_ONLY
+ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ACTIVE_ONLY>;
+ power-domains = <&gcc HLOS1_VOTE_MMNOC_MMU_TBU_SF0_GDSC>;
+ qcom,stream-id-range = <&apps_smmu 0x2000 0x400>;
+ };
+
intc: interrupt-controller@17a00000 {
compatible = "arm,gic-v3";
reg = <0 0x17a00000 0 0x10000>, /* GICD */
^ permalink raw reply related
* [PATCH v7 2/7] iommu/arm-smmu-qcom-tbu: Add Qualcomm TBU driver
From: Georgi Djakov @ 2024-03-29 21:06 UTC (permalink / raw)
To: will, robin.murphy, joro, iommu
Cc: robh+dt, krzysztof.kozlowski+dt, conor+dt, devicetree, andersson,
konrad.dybcio, robdclark, linux-arm-kernel, linux-kernel,
linux-arm-msm, quic_cgoldswo, quic_sukadev, quic_pdaly,
quic_sudaraja, djakov
In-Reply-To: <20240329210638.3647523-1-quic_c_gdjako@quicinc.com>
Operating the TBUs (Translation Buffer Units) from Linux on Qualcomm
platforms can help with debugging context faults. To help with that,
the TBUs can run ATOS (Address Translation Operations) to manually
trigger address translation of IOVA to physical address in hardware
and provide more details when a context fault happens.
The driver will control the resources needed by the TBU to allow
running the debug operations such as ATOS, check for outstanding
transactions, do snapshot capture etc.
Signed-off-by: Georgi Djakov <quic_c_gdjako@quicinc.com>
---
drivers/iommu/Kconfig | 9 +
drivers/iommu/arm/arm-smmu/Makefile | 1 +
.../iommu/arm/arm-smmu/arm-smmu-qcom-tbu.c | 372 ++++++++++++++++++
drivers/iommu/arm/arm-smmu/arm-smmu-qcom.h | 2 +
drivers/iommu/arm/arm-smmu/arm-smmu.h | 2 +
5 files changed, 386 insertions(+)
create mode 100644 drivers/iommu/arm/arm-smmu/arm-smmu-qcom-tbu.c
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index 0af39bbbe3a3..b699e88f42c5 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -374,6 +374,15 @@ config ARM_SMMU_QCOM
When running on a Qualcomm platform that has the custom variant
of the ARM SMMU, this needs to be built into the SMMU driver.
+config ARM_SMMU_QCOM_TBU
+ bool "Qualcomm TBU driver"
+ depends on ARM_SMMU_QCOM
+ help
+ The SMMUs on Qualcomm platforms may include Translation Buffer
+ Units (TBUs) for each master. Enabling support for these units
+ allows to operate the TBUs and obtain additional information
+ when debugging memory management issues like context faults.
+
config ARM_SMMU_QCOM_DEBUG
bool "ARM SMMU QCOM implementation defined debug support"
depends on ARM_SMMU_QCOM
diff --git a/drivers/iommu/arm/arm-smmu/Makefile b/drivers/iommu/arm/arm-smmu/Makefile
index 2a5a95e8e3f9..c35ff78fcfd5 100644
--- a/drivers/iommu/arm/arm-smmu/Makefile
+++ b/drivers/iommu/arm/arm-smmu/Makefile
@@ -3,4 +3,5 @@ obj-$(CONFIG_QCOM_IOMMU) += qcom_iommu.o
obj-$(CONFIG_ARM_SMMU) += arm_smmu.o
arm_smmu-objs += arm-smmu.o arm-smmu-impl.o arm-smmu-nvidia.o
arm_smmu-$(CONFIG_ARM_SMMU_QCOM) += arm-smmu-qcom.o
+arm_smmu-$(CONFIG_ARM_SMMU_QCOM_TBU) += arm-smmu-qcom-tbu.o
arm_smmu-$(CONFIG_ARM_SMMU_QCOM_DEBUG) += arm-smmu-qcom-debug.o
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-tbu.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-tbu.c
new file mode 100644
index 000000000000..e3202ed89566
--- /dev/null
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-tbu.c
@@ -0,0 +1,372 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/interconnect.h>
+#include <linux/iopoll.h>
+#include <linux/list.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#include "arm-smmu.h"
+#include "arm-smmu-qcom.h"
+
+#define TBU_DBG_TIMEOUT_US 100
+#define DEBUG_AXUSER_REG 0x30
+#define DEBUG_AXUSER_CDMID GENMASK_ULL(43, 36)
+#define DEBUG_AXUSER_CDMID_VAL 0xff
+#define DEBUG_PAR_REG 0x28
+#define DEBUG_PAR_FAULT_VAL BIT(0)
+#define DEBUG_PAR_PA GENMASK_ULL(47, 12)
+#define DEBUG_SID_HALT_REG 0x0
+#define DEBUG_SID_HALT_VAL BIT(16)
+#define DEBUG_SID_HALT_SID GENMASK(9, 0)
+#define DEBUG_SR_HALT_ACK_REG 0x20
+#define DEBUG_SR_HALT_ACK_VAL BIT(1)
+#define DEBUG_SR_ECATS_RUNNING_VAL BIT(0)
+#define DEBUG_TXN_AXCACHE GENMASK(5, 2)
+#define DEBUG_TXN_AXPROT GENMASK(8, 6)
+#define DEBUG_TXN_AXPROT_PRIV 0x1
+#define DEBUG_TXN_AXPROT_NSEC 0x2
+#define DEBUG_TXN_TRIGG_REG 0x18
+#define DEBUG_TXN_TRIGGER BIT(0)
+#define DEBUG_VA_ADDR_REG 0x8
+
+static LIST_HEAD(tbu_list);
+static DEFINE_MUTEX(tbu_list_lock);
+static DEFINE_SPINLOCK(atos_lock);
+
+struct qcom_tbu {
+ struct device *dev;
+ struct device_node *smmu_np;
+ u32 sid_range[2];
+ struct list_head list;
+ struct clk *clk;
+ struct icc_path *path;
+ void __iomem *base;
+ spinlock_t halt_lock; /* multiple halt or resume can't execute concurrently */
+ int halt_count;
+};
+
+static struct qcom_smmu *to_qcom_smmu(struct arm_smmu_device *smmu)
+{
+ return container_of(smmu, struct qcom_smmu, smmu);
+}
+
+static struct qcom_tbu *qcom_find_tbu(struct qcom_smmu *qsmmu, u32 sid)
+{
+ struct qcom_tbu *tbu;
+ u32 start, end;
+
+ mutex_lock(&tbu_list_lock);
+
+ if (list_empty(&tbu_list))
+ goto out;
+
+ list_for_each_entry(tbu, &tbu_list, list) {
+ start = tbu->sid_range[0];
+ end = start + tbu->sid_range[1];
+
+ if (qsmmu->smmu.dev->of_node == tbu->smmu_np &&
+ start <= sid && sid < end) {
+ mutex_unlock(&tbu_list_lock);
+ return tbu;
+ }
+ }
+ dev_err(qsmmu->smmu.dev, "Unable to find TBU for sid 0x%x\n", sid);
+
+out:
+ mutex_unlock(&tbu_list_lock);
+ return NULL;
+}
+
+static int qcom_tbu_halt(struct qcom_tbu *tbu, struct arm_smmu_domain *smmu_domain)
+{
+ struct arm_smmu_device *smmu = smmu_domain->smmu;
+ int ret = 0, idx = smmu_domain->cfg.cbndx;
+ unsigned long flags;
+ u32 val, fsr, status;
+
+ spin_lock_irqsave(&tbu->halt_lock, flags);
+ if (tbu->halt_count) {
+ tbu->halt_count++;
+ goto out;
+ }
+
+ val = readl_relaxed(tbu->base + DEBUG_SID_HALT_REG);
+ val |= DEBUG_SID_HALT_VAL;
+ writel_relaxed(val, tbu->base + DEBUG_SID_HALT_REG);
+
+ fsr = arm_smmu_cb_read(smmu, idx, ARM_SMMU_CB_FSR);
+ if ((fsr & ARM_SMMU_FSR_FAULT) && (fsr & ARM_SMMU_FSR_SS)) {
+ u32 sctlr_orig, sctlr;
+
+ /*
+ * We are in a fault. Our request to halt the bus will not
+ * complete until transactions in front of us (such as the fault
+ * itself) have completed. Disable iommu faults and terminate
+ * any existing transactions.
+ */
+ sctlr_orig = arm_smmu_cb_read(smmu, idx, ARM_SMMU_CB_SCTLR);
+ sctlr = sctlr_orig & ~(ARM_SMMU_SCTLR_CFCFG | ARM_SMMU_SCTLR_CFIE);
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_SCTLR, sctlr);
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_FSR, fsr);
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_RESUME, ARM_SMMU_RESUME_TERMINATE);
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_SCTLR, sctlr_orig);
+ }
+
+ if (readl_poll_timeout_atomic(tbu->base + DEBUG_SR_HALT_ACK_REG, status,
+ (status & DEBUG_SR_HALT_ACK_VAL),
+ 0, TBU_DBG_TIMEOUT_US)) {
+ dev_err(tbu->dev, "Timeout while trying to halt TBU!\n");
+ ret = -ETIMEDOUT;
+
+ val = readl_relaxed(tbu->base + DEBUG_SID_HALT_REG);
+ val &= ~DEBUG_SID_HALT_VAL;
+ writel_relaxed(val, tbu->base + DEBUG_SID_HALT_REG);
+
+ goto out;
+ }
+
+ tbu->halt_count = 1;
+
+out:
+ spin_unlock_irqrestore(&tbu->halt_lock, flags);
+ return ret;
+}
+
+static void qcom_tbu_resume(struct qcom_tbu *tbu)
+{
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&tbu->halt_lock, flags);
+ if (!tbu->halt_count) {
+ WARN(1, "%s: halt_count is 0", dev_name(tbu->dev));
+ goto out;
+ }
+
+ if (tbu->halt_count > 1) {
+ tbu->halt_count--;
+ goto out;
+ }
+
+ val = readl_relaxed(tbu->base + DEBUG_SID_HALT_REG);
+ val &= ~DEBUG_SID_HALT_VAL;
+ writel_relaxed(val, tbu->base + DEBUG_SID_HALT_REG);
+
+ tbu->halt_count = 0;
+out:
+ spin_unlock_irqrestore(&tbu->halt_lock, flags);
+}
+
+static phys_addr_t qcom_tbu_trigger_atos(struct arm_smmu_domain *smmu_domain,
+ struct qcom_tbu *tbu, dma_addr_t iova, u32 sid)
+{
+ bool atos_timedout = false;
+ phys_addr_t phys = 0;
+ ktime_t timeout;
+ u64 val;
+
+ /* Set address and stream-id */
+ val = readq_relaxed(tbu->base + DEBUG_SID_HALT_REG);
+ val &= ~DEBUG_SID_HALT_SID;
+ val |= FIELD_PREP(DEBUG_SID_HALT_SID, sid);
+ writeq_relaxed(val, tbu->base + DEBUG_SID_HALT_REG);
+ writeq_relaxed(iova, tbu->base + DEBUG_VA_ADDR_REG);
+ val = FIELD_PREP(DEBUG_AXUSER_CDMID, DEBUG_AXUSER_CDMID_VAL);
+ writeq_relaxed(val, tbu->base + DEBUG_AXUSER_REG);
+
+ /* Write-back read and write-allocate */
+ val = FIELD_PREP(DEBUG_TXN_AXCACHE, 0xf);
+
+ /* Non-secure access */
+ val |= FIELD_PREP(DEBUG_TXN_AXPROT, DEBUG_TXN_AXPROT_NSEC);
+
+ /* Privileged access */
+ val |= FIELD_PREP(DEBUG_TXN_AXPROT, DEBUG_TXN_AXPROT_PRIV);
+
+ val |= DEBUG_TXN_TRIGGER;
+ writeq_relaxed(val, tbu->base + DEBUG_TXN_TRIGG_REG);
+
+ timeout = ktime_add_us(ktime_get(), TBU_DBG_TIMEOUT_US);
+ for (;;) {
+ val = readl_relaxed(tbu->base + DEBUG_SR_HALT_ACK_REG);
+ if (!(val & DEBUG_SR_ECATS_RUNNING_VAL))
+ break;
+ val = readl_relaxed(tbu->base + DEBUG_PAR_REG);
+ if (val & DEBUG_PAR_FAULT_VAL)
+ break;
+ if (ktime_compare(ktime_get(), timeout) > 0) {
+ atos_timedout = true;
+ break;
+ }
+ }
+
+ val = readq_relaxed(tbu->base + DEBUG_PAR_REG);
+ if (val & DEBUG_PAR_FAULT_VAL)
+ dev_err(tbu->dev, "ATOS generated a fault interrupt! PAR = %llx, SID=0x%x\n",
+ val, sid);
+ else if (atos_timedout)
+ dev_err_ratelimited(tbu->dev, "ATOS translation timed out!\n");
+ else
+ phys = FIELD_GET(DEBUG_PAR_PA, val);
+
+ /* Reset hardware */
+ writeq_relaxed(0, tbu->base + DEBUG_TXN_TRIGG_REG);
+ writeq_relaxed(0, tbu->base + DEBUG_VA_ADDR_REG);
+ val = readl_relaxed(tbu->base + DEBUG_SID_HALT_REG);
+ val &= ~DEBUG_SID_HALT_SID;
+ writel_relaxed(val, tbu->base + DEBUG_SID_HALT_REG);
+
+ return phys;
+}
+
+static phys_addr_t qcom_iova_to_phys(struct arm_smmu_domain *smmu_domain,
+ dma_addr_t iova, u32 sid)
+{
+ struct arm_smmu_device *smmu = smmu_domain->smmu;
+ struct qcom_smmu *qsmmu = to_qcom_smmu(smmu);
+ int idx = smmu_domain->cfg.cbndx;
+ struct qcom_tbu *tbu;
+ u32 sctlr_orig, sctlr;
+ phys_addr_t phys = 0;
+ unsigned long flags;
+ int attempt = 0;
+ int ret;
+ u64 fsr;
+
+ tbu = qcom_find_tbu(qsmmu, sid);
+ if (!tbu)
+ return 0;
+
+ ret = icc_set_bw(tbu->path, 0, UINT_MAX);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(tbu->clk);
+ if (ret)
+ goto disable_icc;
+
+ ret = qcom_tbu_halt(tbu, smmu_domain);
+ if (ret)
+ goto disable_clk;
+
+ /*
+ * ATOS/ECATS can trigger the fault interrupt, so disable it temporarily
+ * and check for an interrupt manually.
+ */
+ sctlr_orig = arm_smmu_cb_read(smmu, idx, ARM_SMMU_CB_SCTLR);
+ sctlr = sctlr_orig & ~(ARM_SMMU_SCTLR_CFCFG | ARM_SMMU_SCTLR_CFIE);
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_SCTLR, sctlr);
+
+ fsr = arm_smmu_cb_read(smmu, idx, ARM_SMMU_CB_FSR);
+ if (fsr & ARM_SMMU_FSR_FAULT) {
+ /* Clear pending interrupts */
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_FSR, fsr);
+
+ /*
+ * TBU halt takes care of resuming any stalled transcation.
+ * Kept it here for completeness sake.
+ */
+ if (fsr & ARM_SMMU_FSR_SS)
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_RESUME,
+ ARM_SMMU_RESUME_TERMINATE);
+ }
+
+ /* Only one concurrent atos operation */
+ spin_lock_irqsave(&atos_lock, flags);
+
+ /*
+ * If the translation fails, attempt the lookup more time."
+ */
+ do {
+ phys = qcom_tbu_trigger_atos(smmu_domain, tbu, iova, sid);
+
+ fsr = arm_smmu_cb_read(smmu, idx, ARM_SMMU_CB_FSR);
+ if (fsr & ARM_SMMU_FSR_FAULT) {
+ /* Clear pending interrupts */
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_FSR, fsr);
+
+ if (fsr & ARM_SMMU_FSR_SS)
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_RESUME,
+ ARM_SMMU_RESUME_TERMINATE);
+ }
+ } while (!phys && attempt++ < 2);
+
+ arm_smmu_cb_write(smmu, idx, ARM_SMMU_CB_SCTLR, sctlr_orig);
+ spin_unlock_irqrestore(&atos_lock, flags);
+ qcom_tbu_resume(tbu);
+
+ /* Read to complete prior write transcations */
+ readl_relaxed(tbu->base + DEBUG_SR_HALT_ACK_REG);
+
+disable_clk:
+ clk_disable_unprepare(tbu->clk);
+disable_icc:
+ icc_set_bw(tbu->path, 0, 0);
+
+ return phys;
+}
+
+static int qcom_tbu_probe(struct platform_device *pdev)
+{
+ struct of_phandle_args args = { .args_count = 2 };
+ struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ struct qcom_tbu *tbu;
+
+ tbu = devm_kzalloc(dev, sizeof(*tbu), GFP_KERNEL);
+ if (!tbu)
+ return -ENOMEM;
+
+ tbu->dev = dev;
+ INIT_LIST_HEAD(&tbu->list);
+ spin_lock_init(&tbu->halt_lock);
+
+ if (of_parse_phandle_with_args(np, "qcom,stream-id-range", "#iommu-cells", 0, &args)) {
+ dev_err(dev, "Cannot parse the 'qcom,stream-id-range' DT property\n");
+ return -EINVAL;
+ }
+
+ tbu->smmu_np = args.np;
+ tbu->sid_range[0] = args.args[0];
+ tbu->sid_range[1] = args.args[1];
+ of_node_put(args.np);
+
+ tbu->base = devm_of_iomap(dev, np, 0, NULL);
+ if (IS_ERR(tbu->base))
+ return PTR_ERR(tbu->base);
+
+ tbu->clk = devm_clk_get_optional(dev, NULL);
+ if (IS_ERR(tbu->clk))
+ return PTR_ERR(tbu->clk);
+
+ tbu->path = devm_of_icc_get(dev, NULL);
+ if (IS_ERR(tbu->path))
+ return PTR_ERR(tbu->path);
+
+ mutex_lock(&tbu_list_lock);
+ list_add_tail(&tbu->list, &tbu_list);
+ mutex_unlock(&tbu_list_lock);
+
+ return 0;
+}
+
+static const struct of_device_id qcom_tbu_of_match[] = {
+ { .compatible = "qcom,sc7280-tbu" },
+ { .compatible = "qcom,sdm845-tbu" },
+ { }
+};
+
+static struct platform_driver qcom_tbu_driver = {
+ .driver = {
+ .name = "qcom_tbu",
+ .of_match_table = qcom_tbu_of_match,
+ },
+ .probe = qcom_tbu_probe,
+};
+builtin_platform_driver(qcom_tbu_driver);
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.h b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.h
index 593910567b88..9bb3ae7d62da 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.h
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.h
@@ -30,6 +30,8 @@ struct qcom_smmu_match_data {
const struct arm_smmu_impl *adreno_impl;
};
+irqreturn_t qcom_smmu_context_fault(int irq, void *dev);
+
#ifdef CONFIG_ARM_SMMU_QCOM_DEBUG
void qcom_smmu_tlb_sync_debug(struct arm_smmu_device *smmu);
#else
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.h b/drivers/iommu/arm/arm-smmu/arm-smmu.h
index 836ed6799a80..1670e95c4637 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu.h
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu.h
@@ -136,6 +136,7 @@ enum arm_smmu_cbar_type {
#define ARM_SMMU_CBAR_VMID GENMASK(7, 0)
#define ARM_SMMU_GR1_CBFRSYNRA(n) (0x400 + ((n) << 2))
+#define ARM_SMMU_CBFRSYNRA_SID GENMASK(15, 0)
#define ARM_SMMU_GR1_CBA2R(n) (0x800 + ((n) << 2))
#define ARM_SMMU_CBA2R_VMID16 GENMASK(31, 16)
@@ -238,6 +239,7 @@ enum arm_smmu_cbar_type {
#define ARM_SMMU_CB_ATSR 0x8f0
#define ARM_SMMU_ATSR_ACTIVE BIT(0)
+#define ARM_SMMU_RESUME_TERMINATE BIT(0)
/* Maximum number of context banks per SMMU */
#define ARM_SMMU_MAX_CBS 128
^ permalink raw reply related
* Re: [PATCH 1/5] spi: cadence: Add new bindings documentation for Cadence XSPI
From: Rob Herring @ 2024-03-29 21:09 UTC (permalink / raw)
To: Witold Sadowski
Cc: conor+dt, linux-spi, krzysztof.kozlowski+dt, pthombar,
linux-kernel, broonie, devicetree
In-Reply-To: <20240329194849.25554-2-wsadowski@marvell.com>
On Fri, 29 Mar 2024 12:48:45 -0700, Witold Sadowski wrote:
> Add new bindings:
> - mrvl,xspi-nor compatible string
> Compatible string to enable Marvell XSPI modification
> - Multiple PHY configuration registers
> - base for xfer register set
>
> Signed-off-by: Witold Sadowski <wsadowski@marvell.com>
> ---
> .../devicetree/bindings/spi/cdns,xspi.yaml | 84 ++++++++++++++++++-
> 1 file changed, 83 insertions(+), 1 deletion(-)
>
My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
on your patch (DT_CHECKER_FLAGS is new in v5.13):
yamllint warnings/errors:
dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/spi/cdns,xspi.yaml: properties:compatible: [{'const': 'cdns,xspi-nor'}, {'const': 'mrvl,xspi-nor'}] is not of type 'object', 'boolean'
from schema $id: http://json-schema.org/draft-07/schema#
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/spi/cdns,xspi.yaml: properties:compatible: [{'const': 'cdns,xspi-nor'}, {'const': 'mrvl,xspi-nor'}] is not of type 'object', 'boolean'
from schema $id: http://devicetree.org/meta-schemas/keywords.yaml#
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/spi/cdns,xspi.yaml: ignoring, error in schema: properties: compatible
Error: Documentation/devicetree/bindings/spi/cdns,xspi.example.dts:88.23-24 syntax error
FATAL ERROR: Unable to parse input tree
make[2]: *** [scripts/Makefile.lib:427: Documentation/devicetree/bindings/spi/cdns,xspi.example.dtb] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [/builds/robherring/dt-review-ci/linux/Makefile:1430: dt_binding_check] Error 2
make: *** [Makefile:240: __sub-make] Error 2
doc reference errors (make refcheckdocs):
See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20240329194849.25554-2-wsadowski@marvell.com
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply
* Re: [PATCH] arm64: dts: qcom: qcm6490-idp: Add change to name the regulators
From: Elliot Berman @ 2024-03-29 21:09 UTC (permalink / raw)
To: Umang Chheda
Cc: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, linux-arm-msm, devicetree, linux-kernel,
quic_kamalw
In-Reply-To: <20240329122940.3649730-1-quic_uchheda@quicinc.com>
Nit: the subject line should be:
arm64: dts: qcom: qcm6490: Name the regulators
I don't know if it merits a resend, though.
On Fri, Mar 29, 2024 at 05:59:40PM +0530, Umang Chheda wrote:
> Without explicitly specifying names for the regulators they are named
> based on the DeviceTree node name. This results in multiple regulators
> with the same name, making it impossible to reason debug prints and
> regulator_summary.
>
> Signed-off-by: Umang Chheda <quic_uchheda@quicinc.com>
> ---
> arch/arm64/boot/dts/qcom/qcm6490-idp.dts | 41 ++++++++++++++++++++++++
> 1 file changed, 41 insertions(+)
>
> diff --git a/arch/arm64/boot/dts/qcom/qcm6490-idp.dts b/arch/arm64/boot/dts/qcom/qcm6490-idp.dts
> index f8f8a43f638d..ac6d741868ca 100644
> --- a/arch/arm64/boot/dts/qcom/qcm6490-idp.dts
> +++ b/arch/arm64/boot/dts/qcom/qcm6490-idp.dts
> @@ -195,129 +195,151 @@ regulators-0 {
> vdd-l14-l16-supply = <&vreg_s8b_1p272>;
>
> vreg_s1b_1p872: smps1 {
> + regulator-name = "vreg_s1b_1p872";
> regulator-min-microvolt = <1840000>;
> regulator-max-microvolt = <2040000>;
> };
>
> vreg_s2b_0p876: smps2 {
> + regulator-name = "vreg_s2b_0p876";
> regulator-min-microvolt = <570070>;
> regulator-max-microvolt = <1050000>;
> };
>
> vreg_s7b_0p972: smps7 {
> + regulator-name = "vreg_s7b_0p972";
> regulator-min-microvolt = <535000>;
> regulator-max-microvolt = <1120000>;
> };
>
> vreg_s8b_1p272: smps8 {
> + regulator-name = "vreg_s8b_1p272";
> regulator-min-microvolt = <1200000>;
> regulator-max-microvolt = <1500000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_RET>;
> };
>
> vreg_l1b_0p912: ldo1 {
> + regulator-name = "vreg_l1b_0p912";
> regulator-min-microvolt = <825000>;
> regulator-max-microvolt = <925000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l2b_3p072: ldo2 {
> + regulator-name = "vreg_l2b_3p072";
> regulator-min-microvolt = <2700000>;
> regulator-max-microvolt = <3544000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l3b_0p504: ldo3 {
> + regulator-name = "vreg_l3b_0p504";
> regulator-min-microvolt = <312000>;
> regulator-max-microvolt = <910000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l4b_0p752: ldo4 {
> + regulator-name = "vreg_l4b_0p752";
> regulator-min-microvolt = <752000>;
> regulator-max-microvolt = <820000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> reg_l5b_0p752: ldo5 {
> + regulator-name = "reg_l5b_0p752";
> regulator-min-microvolt = <552000>;
> regulator-max-microvolt = <832000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l6b_1p2: ldo6 {
> + regulator-name = "vreg_l6b_1p2";
> regulator-min-microvolt = <1140000>;
> regulator-max-microvolt = <1260000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l7b_2p952: ldo7 {
> + regulator-name = "vreg_l7b_2p952";
> regulator-min-microvolt = <2400000>;
> regulator-max-microvolt = <3544000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l8b_0p904: ldo8 {
> + regulator-name = "vreg_l8b_0p904";
> regulator-min-microvolt = <870000>;
> regulator-max-microvolt = <970000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l9b_1p2: ldo9 {
> + regulator-name = "vreg_l9b_1p2";
> regulator-min-microvolt = <1200000>;
> regulator-max-microvolt = <1304000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l11b_1p504: ldo11 {
> + regulator-name = "vreg_l11b_1p504";
> regulator-min-microvolt = <1504000>;
> regulator-max-microvolt = <2000000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l12b_0p751: ldo12 {
> + regulator-name = "vreg_l12b_0p751";
> regulator-min-microvolt = <751000>;
> regulator-max-microvolt = <824000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l13b_0p53: ldo13 {
> + regulator-name = "vreg_l13b_0p53";
> regulator-min-microvolt = <530000>;
> regulator-max-microvolt = <824000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l14b_1p08: ldo14 {
> + regulator-name = "vreg_l14b_1p08";
> regulator-min-microvolt = <1080000>;
> regulator-max-microvolt = <1304000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l15b_0p765: ldo15 {
> + regulator-name = "vreg_l15b_0p765";
> regulator-min-microvolt = <765000>;
> regulator-max-microvolt = <1020000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l16b_1p1: ldo16 {
> + regulator-name = "vreg_l16b_1p1";
> regulator-min-microvolt = <1100000>;
> regulator-max-microvolt = <1300000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l17b_1p7: ldo17 {
> + regulator-name = "vreg_l17b_1p7";
> regulator-min-microvolt = <1700000>;
> regulator-max-microvolt = <1900000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l18b_1p8: ldo18 {
> + regulator-name = "vreg_l18b_1p8";
> regulator-min-microvolt = <1800000>;
> regulator-max-microvolt = <2000000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l19b_1p8: ldo19 {
> + regulator-name = "vreg_l19b_1p8";
> regulator-min-microvolt = <1800000>;
> regulator-max-microvolt = <2000000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> @@ -349,109 +371,128 @@ regulators-1 {
> vdd-bob-supply = <&vph_pwr>;
>
> vreg_s1c_2p19: smps1 {
> + regulator-name = "vreg_s1c_2p19";
> regulator-min-microvolt = <2190000>;
> regulator-max-microvolt = <2210000>;
> };
>
> vreg_s2c_0p752: smps2 {
> + regulator-name = "vreg_s2c_0p752";
> regulator-min-microvolt = <750000>;
> regulator-max-microvolt = <800000>;
> };
>
> vreg_s5c_0p752: smps5 {
> + regulator-name = "vreg_s5c_0p752";
> regulator-min-microvolt = <465000>;
> regulator-max-microvolt = <1050000>;
> };
>
> vreg_s7c_0p752: smps7 {
> + regulator-name = "vreg_s7c_0p752";
> regulator-min-microvolt = <465000>;
> regulator-max-microvolt = <800000>;
> };
>
> vreg_s9c_1p084: smps9 {
> + regulator-name = "vreg_s9c_1p084";
> regulator-min-microvolt = <1010000>;
> regulator-max-microvolt = <1170000>;
> };
>
> vreg_l1c_1p8: ldo1 {
> + regulator-name = "vreg_l1c_1p8";
> regulator-min-microvolt = <1800000>;
> regulator-max-microvolt = <1980000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l2c_1p62: ldo2 {
> + regulator-name = "vreg_l2c_1p62";
> regulator-min-microvolt = <1620000>;
> regulator-max-microvolt = <1980000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l3c_2p8: ldo3 {
> + regulator-name = "vreg_l3c_2p8";
> regulator-min-microvolt = <2800000>;
> regulator-max-microvolt = <3540000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l4c_1p62: ldo4 {
> + regulator-name = "vreg_l4c_1p62";
> regulator-min-microvolt = <1620000>;
> regulator-max-microvolt = <3300000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l5c_1p62: ldo5 {
> + regulator-name = "vreg_l5c_1p62";
> regulator-min-microvolt = <1620000>;
> regulator-max-microvolt = <3300000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l6c_2p96: ldo6 {
> + regulator-name = "vreg_l6c_2p96";
> regulator-min-microvolt = <1650000>;
> regulator-max-microvolt = <3544000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l7c_3p0: ldo7 {
> + regulator-name = "vreg_l7c_3p0";
> regulator-min-microvolt = <3000000>;
> regulator-max-microvolt = <3544000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l8c_1p62: ldo8 {
> + regulator-name = "vreg_l8c_1p62";
> regulator-min-microvolt = <1620000>;
> regulator-max-microvolt = <2000000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l9c_2p96: ldo9 {
> + regulator-name = "vreg_l9c_2p96";
> regulator-min-microvolt = <2700000>;
> regulator-max-microvolt = <35440000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l10c_0p88: ldo10 {
> + regulator-name = "vreg_l10c_0p88";
> regulator-min-microvolt = <720000>;
> regulator-max-microvolt = <1050000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l11c_2p8: ldo11 {
> + regulator-name = "vreg_l11c_2p8";
> regulator-min-microvolt = <2800000>;
> regulator-max-microvolt = <3544000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l12c_1p65: ldo12 {
> + regulator-name = "vreg_l12c_1p65";
> regulator-min-microvolt = <1650000>;
> regulator-max-microvolt = <2000000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_l13c_2p7: ldo13 {
> + regulator-name = "vreg_l13c_2p7";
> regulator-min-microvolt = <2700000>;
> regulator-max-microvolt = <3544000>;
> regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
> };
>
> vreg_bob_3p296: bob {
> + regulator-name = "vreg_bob_3p296";
> regulator-min-microvolt = <3008000>;
> regulator-max-microvolt = <3960000>;
> };
> --
> 2.25.1
>
>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox