[PATCH 2/2] iio: frequency: add amlogic clock measure support

Jerome Brunet jbrunet at baylibre.com
Mon Jun 24 10:31:03 PDT 2024


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 at baylibre.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 at baylibre.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 at baylibre.com>");
+MODULE_LICENSE("GPL");
-- 
2.43.0




More information about the linux-amlogic mailing list