[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20240624173105.909554-3-jbrunet@baylibre.com>
Date: Mon, 24 Jun 2024 19:31:03 +0200
From: Jerome Brunet <jbrunet@...libre.com>
To: Jonathan Cameron <jic23@...nel.org>,
Lars-Peter Clausen <lars@...afoo.de>,
Neil Armstrong <neil.armstrong@...aro.org>
Cc: Jerome Brunet <jbrunet@...libre.com>,
Kevin Hilman <khilman@...libre.com>,
linux-kernel@...r.kernel.org,
linux-amlogic@...ts.infradead.org,
linux-iio@...r.kernel.org,
Rob Herring <robh@...nel.org>,
Krzysztof Kozlowski <krzk+dt@...nel.org>,
Conor Dooley <conor+dt@...nel.org>
Subject: [PATCH 2/2] iio: frequency: add amlogic clock measure support
Add support for the HW found in most Amlogic SoC dedicated to measure
system clocks.
This drivers aims to replace the one found in
drivers/soc/amlogic/meson-clk-measure.c with following improvements:
* Access to the measurements through the IIO API:
Easier re-use of the results in userspace and other drivers
* Controllable scale with raw measurements
* Higher precision with processed measurements
Signed-off-by: Jerome Brunet <jbrunet@...libre.com>
---
drivers/iio/frequency/Kconfig | 15 +
drivers/iio/frequency/Makefile | 1 +
drivers/iio/frequency/amlogic-clk-msr-io.c | 802 +++++++++++++++++++++
3 files changed, 818 insertions(+)
create mode 100644 drivers/iio/frequency/amlogic-clk-msr-io.c
diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
index c455be7d4a1c..1682cbec0edf 100644
--- a/drivers/iio/frequency/Kconfig
+++ b/drivers/iio/frequency/Kconfig
@@ -7,6 +7,21 @@
#
# When adding new entries keep the list in alphabetical order
+menu "Frequency Measurements"
+
+config AMLOGIC_CLK_MSR_IO
+ tristate "Amlogic IO Clock Measure"
+ default ARCH_MESON
+ depends on REGMAP_MMIO && COMMON_CLK
+ help
+ Say yes here to build support for Amlogic Clock Measure
+ HW found in Amlogic SoC, with IIO support
+
+ To compile this driver as a module, choose M here: the
+ module will be called amlogic-clk-msr-io.
+
+endmenu
+
menu "Frequency Synthesizers DDS/PLL"
menu "Clock Generator/Distribution"
diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile
index 70d0e0b70e80..3939970cccf3 100644
--- a/drivers/iio/frequency/Makefile
+++ b/drivers/iio/frequency/Makefile
@@ -13,3 +13,4 @@ obj-$(CONFIG_ADMV1013) += admv1013.o
obj-$(CONFIG_ADMV1014) += admv1014.o
obj-$(CONFIG_ADMV4420) += admv4420.o
obj-$(CONFIG_ADRF6780) += adrf6780.o
+obj-$(CONFIG_AMLOGIC_CLK_MSR_IO) += amlogic-clk-msr-io.o
diff --git a/drivers/iio/frequency/amlogic-clk-msr-io.c b/drivers/iio/frequency/amlogic-clk-msr-io.c
new file mode 100644
index 000000000000..16a15dd346d8
--- /dev/null
+++ b/drivers/iio/frequency/amlogic-clk-msr-io.c
@@ -0,0 +1,802 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2024 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@...libre.com>
+
+#include <linux/bitfield.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MSR_CLK_REG0 0x0
+#define MSR_BUSY BIT(31)
+#define MSR_CLK_SRC GENMASK(26, 20)
+#define MSR_CLK_EN BIT(19)
+#define MSR_CONT BIT(17)
+#define MSR_ENABLE BIT(16)
+#define MSR_TIME GENMASK(15, 0)
+#define MSR_TIME_MAX (MSR_TIME + 1)
+#define MSR_CLK_REG1 0x4
+#define MSR_CLK_REG2 0x8
+#define MSR_MEASURED GENMASK(19, 0)
+
+#define CLK_MIN_TIME 64 /* This allows to measure up to 8GHz */
+#define CLK_MSR_MAX 128
+
+/*
+ * Note this driver aims to replace drivers/soc/amlogic/meson-clk-measure.c
+ * It provides the same functionality and adds support for the IIO API.
+ * The only thing missing is the clock summary which is very handy for
+ * debugging purpose. It is easy to reproduce the summary in userspace,
+ * from the iio device sysfs directory:
+ *
+ * for i in $(seq 0 127); do
+ * printf "%20s: %11dHz +/- %.0fHz\n" \
+ * $(cat in_altvoltage${i}_label) \
+ * $(cat in_altvoltage${i}_input) \
+ * $(cat in_altvoltage_scale);
+ * done
+ */
+
+struct amlogic_cmsr {
+ struct regmap *reg;
+ struct regmap *duty;
+ struct mutex lock;
+};
+
+static const char *cmsr_m8[CLK_MSR_MAX] = {
+ [0] = "ring_osc_out_ee0",
+ [1] = "ring_osc_out_ee1",
+ [2] = "ring_osc_out_ee2",
+ [3] = "a9_ring_osck",
+ [6] = "vid_pll",
+ [7] = "clk81",
+ [8] = "encp",
+ [9] = "encl",
+ [11] = "eth_rmii",
+ [13] = "amclk",
+ [14] = "fec_clk_0",
+ [15] = "fec_clk_1",
+ [16] = "fec_clk_2",
+ [18] = "a9_clk_div16",
+ [19] = "hdmi_sys",
+ [20] = "rtc_osc_clk_out",
+ [21] = "i2s_clk_in_src0",
+ [22] = "clk_rmii_from_pad",
+ [23] = "hdmi_ch0_tmds",
+ [24] = "lvds_fifo",
+ [26] = "sc_clk_int",
+ [28] = "sar_adc",
+ [30] = "mpll_clk_test_out",
+ [31] = "audac_clkpi",
+ [32] = "vdac",
+ [33] = "sdhc_rx",
+ [34] = "sdhc_sd",
+ [35] = "mali",
+ [36] = "hdmi_tx_pixel",
+ [38] = "vdin_meas",
+ [39] = "pcm_sclk",
+ [40] = "pcm_mclk",
+ [41] = "eth_rx_tx",
+ [42] = "pwm_d",
+ [43] = "pwm_c",
+ [44] = "pwm_b",
+ [45] = "pwm_a",
+ [46] = "pcm2_sclk",
+ [47] = "ddr_dpll_pt",
+ [48] = "pwm_f",
+ [49] = "pwm_e",
+ [59] = "hcodec",
+ [60] = "usb_32k_alt",
+ [61] = "gpio",
+ [62] = "vid2_pll",
+ [63] = "mipi_csi_cfg",
+};
+
+static const char *cmsr_gx[CLK_MSR_MAX] = {
+ [0] = "ring_osc_out_ee_0",
+ [1] = "ring_osc_out_ee_1",
+ [2] = "ring_osc_out_ee_2",
+ [3] = "a53_ring_osc",
+ [4] = "gp0_pll",
+ [6] = "enci",
+ [7] = "clk81",
+ [8] = "encp",
+ [9] = "encl",
+ [10] = "vdac",
+ [11] = "rgmii_tx",
+ [12] = "pdm",
+ [13] = "amclk",
+ [14] = "fec_0",
+ [15] = "fec_1",
+ [16] = "fec_2",
+ [17] = "sys_pll_div16",
+ [18] = "sys_cpu_div16",
+ [19] = "hdmitx_sys",
+ [20] = "rtc_osc_out",
+ [21] = "i2s_in_src0",
+ [22] = "eth_phy_ref",
+ [23] = "hdmi_todig",
+ [26] = "sc_int",
+ [28] = "sar_adc",
+ [31] = "mpll_test_out",
+ [32] = "vdec",
+ [35] = "mali",
+ [36] = "hdmi_tx_pixel",
+ [37] = "i958",
+ [38] = "vdin_meas",
+ [39] = "pcm_sclk",
+ [40] = "pcm_mclk",
+ [41] = "eth_rx_or_rmii",
+ [42] = "mp0_out",
+ [43] = "fclk_div5",
+ [44] = "pwm_b",
+ [45] = "pwm_a",
+ [46] = "vpu",
+ [47] = "ddr_dpll_pt",
+ [48] = "mp1_out",
+ [49] = "mp2_out",
+ [50] = "mp3_out",
+ [51] = "nand_core",
+ [52] = "sd_emmc_b",
+ [53] = "sd_emmc_a",
+ [55] = "vid_pll_div_out",
+ [56] = "cci",
+ [57] = "wave420l_c",
+ [58] = "wave420l_b",
+ [59] = "hcodec",
+ [60] = "alt_32k",
+ [61] = "gpio_msr",
+ [62] = "hevc",
+ [66] = "vid_lock",
+ [70] = "pwm_f",
+ [71] = "pwm_e",
+ [72] = "pwm_d",
+ [73] = "pwm_c",
+ [75] = "aoclkx2_int",
+ [76] = "aoclk_int",
+ [77] = "rng_ring_osc_0",
+ [78] = "rng_ring_osc_1",
+ [79] = "rng_ring_osc_2",
+ [80] = "rng_ring_osc_3",
+ [81] = "vapb",
+ [82] = "ge2d",
+};
+
+static const char *cmsr_axg[CLK_MSR_MAX] = {
+ [0] = "ring_osc_out_ee_0",
+ [1] = "ring_osc_out_ee_1",
+ [2] = "ring_osc_out_ee_2",
+ [3] = "a53_ring_osc",
+ [4] = "gp0_pll",
+ [5] = "gp1_pll",
+ [7] = "clk81",
+ [9] = "encl",
+ [17] = "sys_pll_div16",
+ [18] = "sys_cpu_div16",
+ [20] = "rtc_osc_out",
+ [23] = "mmc_clk",
+ [28] = "sar_adc",
+ [31] = "mpll_test_out",
+ [40] = "mod_eth_tx_clk",
+ [41] = "mod_eth_rx_clk_rmii",
+ [42] = "mp0_out",
+ [43] = "fclk_div5",
+ [44] = "pwm_b",
+ [45] = "pwm_a",
+ [46] = "vpu",
+ [47] = "ddr_dpll_pt",
+ [48] = "mp1_out",
+ [49] = "mp2_out",
+ [50] = "mp3_out",
+ [51] = "sd_emmm_c",
+ [52] = "sd_emmc_b",
+ [61] = "gpio_msr",
+ [66] = "audio_slv_lrclk_c",
+ [67] = "audio_slv_lrclk_b",
+ [68] = "audio_slv_lrclk_a",
+ [69] = "audio_slv_sclk_c",
+ [70] = "audio_slv_sclk_b",
+ [71] = "audio_slv_sclk_a",
+ [72] = "pwm_d",
+ [73] = "pwm_c",
+ [74] = "wifi_beacon",
+ [75] = "tdmin_lb_lrcl",
+ [76] = "tdmin_lb_sclk",
+ [77] = "rng_ring_osc_0",
+ [78] = "rng_ring_osc_1",
+ [79] = "rng_ring_osc_2",
+ [80] = "rng_ring_osc_3",
+ [81] = "vapb",
+ [82] = "ge2d",
+ [84] = "audio_resample",
+ [85] = "audio_pdm_sys",
+ [86] = "audio_spdifout",
+ [87] = "audio_spdifin",
+ [88] = "audio_lrclk_f",
+ [89] = "audio_lrclk_e",
+ [90] = "audio_lrclk_d",
+ [91] = "audio_lrclk_c",
+ [92] = "audio_lrclk_b",
+ [93] = "audio_lrclk_a",
+ [94] = "audio_sclk_f",
+ [95] = "audio_sclk_e",
+ [96] = "audio_sclk_d",
+ [97] = "audio_sclk_c",
+ [98] = "audio_sclk_b",
+ [99] = "audio_sclk_a",
+ [100] = "audio_mclk_f",
+ [101] = "audio_mclk_e",
+ [102] = "audio_mclk_d",
+ [103] = "audio_mclk_c",
+ [104] = "audio_mclk_b",
+ [105] = "audio_mclk_a",
+ [106] = "pcie_refclk_n",
+ [107] = "pcie_refclk_p",
+ [108] = "audio_locker_out",
+ [109] = "audio_locker_in",
+};
+
+static const char *cmsr_g12a[CLK_MSR_MAX] = {
+ [0] = "ring_osc_out_ee_0",
+ [1] = "ring_osc_out_ee_1",
+ [2] = "ring_osc_out_ee_2",
+ [3] = "sys_cpu_ring_osc",
+ [4] = "gp0_pll",
+ [6] = "enci",
+ [7] = "clk81",
+ [8] = "encp",
+ [9] = "encl",
+ [10] = "vdac",
+ [11] = "eth_tx",
+ [12] = "hifi_pll",
+ [13] = "mod_tcon",
+ [14] = "fec_0",
+ [15] = "fec_1",
+ [16] = "fec_2",
+ [17] = "sys_pll_div16",
+ [18] = "sys_cpu_div16",
+ [19] = "lcd_an_ph2",
+ [20] = "rtc_osc_out",
+ [21] = "lcd_an_ph3",
+ [22] = "eth_phy_ref",
+ [23] = "mpll_50m",
+ [24] = "eth_125m",
+ [25] = "eth_rmii",
+ [26] = "sc_int",
+ [27] = "in_mac",
+ [28] = "sar_adc",
+ [29] = "pcie_inp",
+ [30] = "pcie_inn",
+ [31] = "mpll_test_out",
+ [32] = "vdec",
+ [33] = "sys_cpu_ring_osc_1",
+ [34] = "eth_mpll_50m",
+ [35] = "mali",
+ [36] = "hdmi_tx_pixel",
+ [37] = "cdac",
+ [38] = "vdin_meas",
+ [39] = "bt656",
+ [41] = "eth_rx_or_rmii",
+ [42] = "mp0_out",
+ [43] = "fclk_div5",
+ [44] = "pwm_b",
+ [45] = "pwm_a",
+ [46] = "vpu",
+ [47] = "ddr_dpll_pt",
+ [48] = "mp1_out",
+ [49] = "mp2_out",
+ [50] = "mp3_out",
+ [51] = "sd_emmc_c",
+ [52] = "sd_emmc_b",
+ [53] = "sd_emmc_a",
+ [54] = "vpu_clkc",
+ [55] = "vid_pll_div_out",
+ [56] = "wave420l_a",
+ [57] = "wave420l_c",
+ [58] = "wave420l_b",
+ [59] = "hcodec",
+ [61] = "gpio_msr",
+ [62] = "hevcb",
+ [63] = "dsi_meas",
+ [64] = "spicc_1",
+ [65] = "spicc_0",
+ [66] = "vid_lock",
+ [67] = "dsi_phy",
+ [68] = "hdcp22_esm",
+ [69] = "hdcp22_skp",
+ [70] = "pwm_f",
+ [71] = "pwm_e",
+ [72] = "pwm_d",
+ [73] = "pwm_c",
+ [75] = "hevcf",
+ [77] = "rng_ring_osc_0",
+ [78] = "rng_ring_osc_1",
+ [79] = "rng_ring_osc_2",
+ [80] = "rng_ring_osc_3",
+ [81] = "vapb",
+ [82] = "ge2d",
+ [83] = "co_rx",
+ [84] = "co_tx",
+ [89] = "hdmi_todig",
+ [90] = "hdmitx_sys",
+ [91] = "sys_cpub_div16",
+ [92] = "sys_pll_cpub_div16",
+ [94] = "eth_phy_rx",
+ [95] = "eth_phy_pll",
+ [96] = "vpu_b",
+ [97] = "cpu_b_tmp",
+ [98] = "ts",
+ [99] = "ring_osc_out_ee_3",
+ [100] = "ring_osc_out_ee_4",
+ [101] = "ring_osc_out_ee_5",
+ [102] = "ring_osc_out_ee_6",
+ [103] = "ring_osc_out_ee_7",
+ [104] = "ring_osc_out_ee_8",
+ [105] = "ring_osc_out_ee_9",
+ [106] = "ephy_test",
+ [107] = "au_dac_g128x",
+ [108] = "audio_locker_out",
+ [109] = "audio_locker_in",
+ [110] = "audio_tdmout_c_sclk",
+ [111] = "audio_tdmout_b_sclk",
+ [112] = "audio_tdmout_a_sclk",
+ [113] = "audio_tdmin_lb_sclk",
+ [114] = "audio_tdmin_c_sclk",
+ [115] = "audio_tdmin_b_sclk",
+ [116] = "audio_tdmin_a_sclk",
+ [117] = "audio_resample",
+ [118] = "audio_pdm_sys",
+ [119] = "audio_spdifout_b",
+ [120] = "audio_spdifout",
+ [121] = "audio_spdifin",
+ [122] = "audio_pdm_dclk",
+};
+
+static const char *cmsr_sm1[CLK_MSR_MAX] = {
+ [0] = "ring_osc_out_ee_0",
+ [1] = "ring_osc_out_ee_1",
+ [2] = "ring_osc_out_ee_2",
+ [3] = "ring_osc_out_ee_3",
+ [4] = "gp0_pll",
+ [5] = "gp1_pll",
+ [6] = "enci",
+ [7] = "clk81",
+ [8] = "encp",
+ [9] = "encl",
+ [10] = "vdac",
+ [11] = "eth_tx",
+ [12] = "hifi_pll",
+ [13] = "mod_tcon",
+ [14] = "fec_0",
+ [15] = "fec_1",
+ [16] = "fec_2",
+ [17] = "sys_pll_div16",
+ [18] = "sys_cpu_div16",
+ [19] = "lcd_an_ph2",
+ [20] = "rtc_osc_out",
+ [21] = "lcd_an_ph3",
+ [22] = "eth_phy_ref",
+ [23] = "mpll_50m",
+ [24] = "eth_125m",
+ [25] = "eth_rmii",
+ [26] = "sc_int",
+ [27] = "in_mac",
+ [28] = "sar_adc",
+ [29] = "pcie_inp",
+ [30] = "pcie_inn",
+ [31] = "mpll_test_out",
+ [32] = "vdec",
+ [34] = "eth_mpll_50m",
+ [35] = "mali",
+ [36] = "hdmi_tx_pixel",
+ [37] = "cdac",
+ [38] = "vdin_meas",
+ [39] = "bt656",
+ [40] = "arm_ring_osc_out_4",
+ [41] = "eth_rx_or_rmii",
+ [42] = "mp0_out",
+ [43] = "fclk_div5",
+ [44] = "pwm_b",
+ [45] = "pwm_a",
+ [46] = "vpu",
+ [47] = "ddr_dpll_pt",
+ [48] = "mp1_out",
+ [49] = "mp2_out",
+ [50] = "mp3_out",
+ [51] = "sd_emmc_c",
+ [52] = "sd_emmc_b",
+ [53] = "sd_emmc_a",
+ [54] = "vpu_clkc",
+ [55] = "vid_pll_div_out",
+ [56] = "wave420l_a",
+ [57] = "wave420l_c",
+ [58] = "wave420l_b",
+ [59] = "hcodec",
+ [60] = "arm_ring_osc_out_5",
+ [61] = "gpio_msr",
+ [62] = "hevcb",
+ [63] = "dsi_meas",
+ [64] = "spicc_1",
+ [65] = "spicc_0",
+ [66] = "vid_lock",
+ [67] = "dsi_phy",
+ [68] = "hdcp22_esm",
+ [69] = "hdcp22_skp",
+ [70] = "pwm_f",
+ [71] = "pwm_e",
+ [72] = "pwm_d",
+ [73] = "pwm_c",
+ [74] = "arm_ring_osc_out_6",
+ [75] = "hevcf",
+ [76] = "arm_ring_osc_out_7",
+ [77] = "rng_ring_osc_0",
+ [78] = "rng_ring_osc_1",
+ [79] = "rng_ring_osc_2",
+ [80] = "rng_ring_osc_3",
+ [81] = "vapb",
+ [82] = "ge2d",
+ [83] = "co_rx",
+ [84] = "co_tx",
+ [85] = "arm_ring_osc_out_8",
+ [86] = "arm_ring_osc_out_9",
+ [87] = "mipi_dsi_phy",
+ [88] = "cis2_adapt",
+ [89] = "hdmi_todig",
+ [90] = "hdmitx_sys",
+ [91] = "nna_core",
+ [92] = "nna_axi",
+ [93] = "vad",
+ [94] = "eth_phy_rx",
+ [95] = "eth_phy_pll",
+ [96] = "vpu_b",
+ [97] = "cpu_b_tmp",
+ [98] = "ts",
+ [99] = "arm_ring_osc_out_10",
+ [100] = "arm_ring_osc_out_11",
+ [101] = "arm_ring_osc_out_12",
+ [102] = "arm_ring_osc_out_13",
+ [103] = "arm_ring_osc_out_14",
+ [104] = "arm_ring_osc_out_15",
+ [105] = "arm_ring_osc_out_16",
+ [106] = "ephy_test",
+ [107] = "au_dac_g128x",
+ [108] = "audio_locker_out",
+ [109] = "audio_locker_in",
+ [110] = "audio_tdmout_c_sclk",
+ [111] = "audio_tdmout_b_sclk",
+ [112] = "audio_tdmout_a_sclk",
+ [113] = "audio_tdmin_lb_sclk",
+ [114] = "audio_tdmin_c_sclk",
+ [115] = "audio_tdmin_b_sclk",
+ [116] = "audio_tdmin_a_sclk",
+ [117] = "audio_resample",
+ [118] = "audio_pdm_sys",
+ [119] = "audio_spdifout_b",
+ [120] = "audio_spdifout",
+ [121] = "audio_spdifin",
+ [122] = "audio_pdm_dclk",
+ [123] = "audio_resampled",
+ [124] = "earcrx_pll",
+ [125] = "earcrx_pll_test",
+ [126] = "csi_phy0",
+ [127] = "csi2_data",
+};
+
+static struct iio_chan_spec *cmsr_populate_channels(struct device *dev,
+ const char * const *conf)
+{
+ struct iio_chan_spec *chan;
+ int i;
+
+ chan = devm_kzalloc(dev, sizeof(*chan) * CLK_MSR_MAX, GFP_KERNEL);
+ if (!chan)
+ return ERR_PTR(-ENOMEM);
+
+ for (i = 0; i < CLK_MSR_MAX; i++) {
+ chan[i].type = IIO_ALTVOLTAGE;
+ chan[i].indexed = 1;
+ chan[i].channel = i;
+ chan[i].info_mask_separate = (BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_PROCESSED));
+ chan[i].info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
+ chan[i].datasheet_name = conf[i];
+ }
+
+ return chan;
+}
+
+static int cmsr_get_time_unlocked(struct amlogic_cmsr *cm)
+{
+ unsigned int raw;
+
+ regmap_read(cm->reg, MSR_CLK_REG0, &raw);
+
+ /* Field register value is time = val + 1 */
+ return FIELD_GET(MSR_TIME, raw) + 1;
+}
+
+static int cmsr_set_time_unlocked(struct amlogic_cmsr *cm,
+ unsigned int time)
+{
+ time -= 1;
+
+ if (time < 0 || time > MSR_TIME)
+ return -EINVAL;
+
+ regmap_update_bits(cm->reg, MSR_CLK_REG0, MSR_TIME,
+ FIELD_PREP(MSR_TIME, time));
+
+ return 0;
+}
+
+static int cmsr_measure_unlocked(struct amlogic_cmsr *cm,
+ unsigned int idx)
+{
+ unsigned int raw;
+ int ret;
+
+ regmap_update_bits(cm->reg, MSR_CLK_REG0,
+ MSR_ENABLE | MSR_CONT | MSR_CLK_EN | MSR_CLK_SRC,
+ MSR_CLK_EN | FIELD_PREP(MSR_CLK_SRC, idx));
+
+ regmap_set_bits(cm->reg, MSR_CLK_REG0, MSR_ENABLE);
+ ret = regmap_read_poll_timeout(cm->reg, MSR_CLK_REG0, raw,
+ !(raw & MSR_BUSY), 10, 70000);
+ regmap_clear_bits(cm->reg, MSR_CLK_REG0, MSR_ENABLE | MSR_CLK_EN);
+
+ if (ret)
+ return ret;
+
+ regmap_read(cm->reg, MSR_CLK_REG2, &raw);
+ ret = FIELD_GET(MSR_MEASURED, raw);
+
+ /* Check for overflow */
+ if (ret == MSR_MEASURED)
+ return -EINVAL;
+
+ return ret;
+}
+
+static int cmsr_measure_processed_unlocked(struct amlogic_cmsr *cm,
+ unsigned int idx,
+ int *val2)
+{
+ unsigned int time = CLK_MIN_TIME;
+ u64 rate;
+ int ret;
+
+ /*
+ * The challenge here is to provide the best accuracy
+ * while not spending to much time doing it.
+ * - Starting with a short duration risk not detecting
+ * slow clocks, but it is fast. All 128 can be done in ~8ms
+ * - Starting with a long duration risk overflowing the
+ * measurement counter and would be way to long, especially
+ * considering the number of disabled clocks. ~4s for all
+ * 128 worst case.
+ *
+ * This IP measures system clocks so all clock are expected
+ * to be 1kHz < f < 8GHz. We can compromise based on this,
+ * doing it in 3 pass:
+ * #1 Starting if 64us window: detects 30kHz < f < 8GHz
+ * - Go to #2 if no detection, Go to #3 otherwise
+ * #2 Extend duration to 1024us (f > 1kHz)
+ - Assume f = 0Hz if no detection, Go to #3 otherwise
+ * #3 Clock has been detected, adjust window for best accuracy
+ *
+ * Doing the range detection takes ~1ms per clock, including disabled
+ clocks.
+ * Actual measurement takes at most ~65ms in step #3 for slow clocks,
+ * when the full range the HW is used.
+ */
+
+ /* Step #1 - quick measurement */
+ cmsr_set_time_unlocked(cm, time);
+ ret = cmsr_measure_unlocked(cm, idx);
+ if (ret < 0)
+ return ret;
+
+ else if (ret == 0) {
+ /* Step #2 - extend the window if necessary */
+ time *= 16;
+ cmsr_set_time_unlocked(cm, time);
+ ret = cmsr_measure_unlocked(cm, idx);
+ if (ret < 0)
+ return ret;
+
+ else if (ret == 0) {
+ /* Still nothing - assume no clock */
+ *val2 = 0;
+ return 0;
+ }
+ }
+
+ /* Step #3: Adapt scale for better precision */
+ time = time * MSR_MEASURED * 3 / (ret * 4); /* 25% margin */
+ time = min_t(unsigned int, MSR_TIME_MAX, time);
+
+ /* Actually measure rate with an optimized scale */
+ cmsr_set_time_unlocked(cm, time);
+ ret = cmsr_measure_unlocked(cm, idx);
+ if (ret < 0)
+ return ret;
+
+ rate = DIV_ROUND_CLOSEST_ULL(ret * 1000000ULL, time);
+ *val2 = rate >> 32ULL;
+ return (int)rate;
+}
+
+static int cmsr_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct amlogic_cmsr *cm = iio_priv(indio_dev);
+
+ guard(mutex)(&cm->lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *val = cmsr_measure_unlocked(cm, chan->channel);
+ if (*val < 0)
+ return *val;
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_PROCESSED: /* Result in Hz */
+ *val = cmsr_measure_processed_unlocked(cm, chan->channel, val2);
+ if (*val < 0)
+ return *val;
+ return IIO_VAL_INT_64;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val2 = cmsr_get_time_unlocked(cm);
+ *val = 1000000;
+ return IIO_VAL_FRACTIONAL;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int cmsr_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct amlogic_cmsr *cm = iio_priv(indio_dev);
+ unsigned int time;
+
+ guard(mutex)(&cm->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ time = DIV_ROUND_CLOSEST(1000000U, val);
+ return cmsr_set_time_unlocked(cm, time);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int cmsr_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long info)
+{
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT;
+ default:
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+}
+
+static int cmsr_read_label(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ char *label)
+{
+ return sprintf(label, "%s\n", chan->datasheet_name);
+}
+
+static const struct of_device_id cmsr_of_match[] = {
+ {
+ .compatible = "amlogic,gx-clk-msr-io",
+ .data = cmsr_gx,
+ }, {
+ .compatible = "amlogic,meson8-clk-msr-io",
+ .data = cmsr_m8,
+ }, {
+ .compatible = "amlogic,axg-clk-msr-io",
+ .data = cmsr_axg,
+ }, {
+ .compatible = "amlogic,g12a-clk-msr-io",
+ .data = cmsr_g12a,
+ }, {
+ .compatible = "amlogic,sm1-clk-msr-io",
+ .data = cmsr_sm1,
+ }, {}
+};
+MODULE_DEVICE_TABLE(of, cmsr_of_match);
+
+static const struct iio_info cmsr_info = {
+ .read_raw = cmsr_read_raw,
+ .read_label = cmsr_read_label,
+ .write_raw = cmsr_write_raw,
+ .write_raw_get_fmt = cmsr_write_raw_get_fmt,
+};
+
+static const struct regmap_config cmsr_regmap_cfg = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static int cmsr_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct iio_dev *indio_dev;
+ struct amlogic_cmsr *cm;
+ const char * const *conf;
+ void __iomem *regs;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*cm));
+ if (!indio_dev)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, indio_dev);
+ cm = iio_priv(indio_dev);
+
+ conf = of_device_get_match_data(dev);
+ if (!conf) {
+ dev_err(dev, "failed to match device\n");
+ return -ENODEV;
+ }
+
+ regs = devm_platform_ioremap_resource_byname(pdev, "reg");
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ cm->reg = devm_regmap_init_mmio(dev, regs, &cmsr_regmap_cfg);
+ if (IS_ERR(cm->reg)) {
+ dev_err(dev, "failed to init main regmap: %ld\n",
+ PTR_ERR(cm->reg));
+ return PTR_ERR(cm->reg);
+ }
+
+ regs = devm_platform_ioremap_resource_byname(pdev, "duty");
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ cm->duty = devm_regmap_init_mmio(dev, regs, &cmsr_regmap_cfg);
+ if (IS_ERR(cm->duty)) {
+ dev_err(dev, "failed to init duty regmap: %ld\n",
+ PTR_ERR(cm->duty));
+ return PTR_ERR(cm->duty);
+ }
+
+ mutex_init(&cm->lock);
+
+ /* Init scale with a sane default */
+ cmsr_set_time_unlocked(cm, CLK_MIN_TIME);
+
+ indio_dev->name = "amlogic-clk-msr";
+ indio_dev->info = &cmsr_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->num_channels = CLK_MSR_MAX;
+ indio_dev->channels = cmsr_populate_channels(dev, conf);
+ if (IS_ERR(indio_dev->channels))
+ return PTR_ERR(indio_dev->channels);
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static struct platform_driver amlogic_cmsr_driver = {
+ .probe = cmsr_probe,
+ .driver = {
+ .name = "amlogic-clk-msr-io",
+ .of_match_table = cmsr_of_match,
+ },
+};
+module_platform_driver(amlogic_cmsr_driver);
+
+MODULE_DESCRIPTION("Amlogic Clock Measure IO driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@...libre.com>");
+MODULE_LICENSE("GPL");
--
2.43.0
Powered by blists - more mailing lists