[v3,2/2] soc: qcom: Add Qualcomm Ramp Controller driver
Commit Message
The Ramp Controller is used to program the sequence ID for pulse
swallowing, enable sequence and linking sequence IDs for the CPU
cores on some Qualcomm SoCs.
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
drivers/soc/qcom/Kconfig | 9 +
drivers/soc/qcom/Makefile | 1 +
drivers/soc/qcom/ramp_controller.c | 331 +++++++++++++++++++++++++++++
3 files changed, 341 insertions(+)
create mode 100644 drivers/soc/qcom/ramp_controller.c
Comments
On 15/11/2022 18:45, AngeloGioacchino Del Regno wrote:
> The Ramp Controller is used to program the sequence ID for pulse
> swallowing, enable sequence and linking sequence IDs for the CPU
> cores on some Qualcomm SoCs.
>
> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
> ---
> drivers/soc/qcom/Kconfig | 9 +
> drivers/soc/qcom/Makefile | 1 +
> drivers/soc/qcom/ramp_controller.c | 331 +++++++++++++++++++++++++++++
> 3 files changed, 341 insertions(+)
> create mode 100644 drivers/soc/qcom/ramp_controller.c
>
I'd allow myself to re-ask the same questions here:
Generic question. regarding this controller. If it is supposed to work
close to DVCS, etc. Does ramp controller need any programming when
changing speed and/or APC voltage?
Is it necessary to turn ramp on and off during the runtime?
Hi AngeloGioacchino,
Thank you for the patch! Yet something to improve:
[auto build test ERROR on robh/for-next]
[also build test ERROR on linus/master v6.1-rc5 next-20221116]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/AngeloGioacchino-Del-Regno/Qualcomm-Ramp-Controller-and-MSM8976-config/20221115-234706
base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
patch link: https://lore.kernel.org/r/20221115154555.324437-3-angelogioacchino.delregno%40collabora.com
patch subject: [PATCH v3 2/2] soc: qcom: Add Qualcomm Ramp Controller driver
config: s390-randconfig-c005-20221116
compiler: clang version 16.0.0 (https://github.com/llvm/llvm-project 463da45892e2d2a262277b91b96f5f8c05dc25d0)
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# install s390 cross compiling tool for clang build
# apt-get install binutils-s390x-linux-gnu
# https://github.com/intel-lab-lkp/linux/commit/b5780f95b36cf32d6ea07cc191a665718d4e4539
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review AngeloGioacchino-Del-Regno/Qualcomm-Ramp-Controller-and-MSM8976-config/20221115-234706
git checkout b5780f95b36cf32d6ea07cc191a665718d4e4539
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=s390 SHELL=/bin/bash drivers/soc/qcom/
If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>
All errors (new ones prefixed by >>):
In file included from drivers/soc/qcom/ramp_controller.c:13:
In file included from include/linux/regmap.h:20:
In file included from include/linux/iopoll.h:14:
In file included from include/linux/io.h:13:
In file included from arch/s390/include/asm/io.h:75:
include/asm-generic/io.h:547:31: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
val = __raw_readb(PCI_IOBASE + addr);
~~~~~~~~~~ ^
include/asm-generic/io.h:560:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
val = __le16_to_cpu((__le16 __force)__raw_readw(PCI_IOBASE + addr));
~~~~~~~~~~ ^
include/uapi/linux/byteorder/big_endian.h:37:59: note: expanded from macro '__le16_to_cpu'
#define __le16_to_cpu(x) __swab16((__force __u16)(__le16)(x))
^
include/uapi/linux/swab.h:102:54: note: expanded from macro '__swab16'
#define __swab16(x) (__u16)__builtin_bswap16((__u16)(x))
^
In file included from drivers/soc/qcom/ramp_controller.c:13:
In file included from include/linux/regmap.h:20:
In file included from include/linux/iopoll.h:14:
In file included from include/linux/io.h:13:
In file included from arch/s390/include/asm/io.h:75:
include/asm-generic/io.h:573:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
val = __le32_to_cpu((__le32 __force)__raw_readl(PCI_IOBASE + addr));
~~~~~~~~~~ ^
include/uapi/linux/byteorder/big_endian.h:35:59: note: expanded from macro '__le32_to_cpu'
#define __le32_to_cpu(x) __swab32((__force __u32)(__le32)(x))
^
include/uapi/linux/swab.h:115:54: note: expanded from macro '__swab32'
#define __swab32(x) (__u32)__builtin_bswap32((__u32)(x))
^
In file included from drivers/soc/qcom/ramp_controller.c:13:
In file included from include/linux/regmap.h:20:
In file included from include/linux/iopoll.h:14:
In file included from include/linux/io.h:13:
In file included from arch/s390/include/asm/io.h:75:
include/asm-generic/io.h:584:33: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
__raw_writeb(value, PCI_IOBASE + addr);
~~~~~~~~~~ ^
include/asm-generic/io.h:594:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
__raw_writew((u16 __force)cpu_to_le16(value), PCI_IOBASE + addr);
~~~~~~~~~~ ^
include/asm-generic/io.h:604:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
__raw_writel((u32 __force)cpu_to_le32(value), PCI_IOBASE + addr);
~~~~~~~~~~ ^
include/asm-generic/io.h:692:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
readsb(PCI_IOBASE + addr, buffer, count);
~~~~~~~~~~ ^
include/asm-generic/io.h:700:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
readsw(PCI_IOBASE + addr, buffer, count);
~~~~~~~~~~ ^
include/asm-generic/io.h:708:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
readsl(PCI_IOBASE + addr, buffer, count);
~~~~~~~~~~ ^
include/asm-generic/io.h:717:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
writesb(PCI_IOBASE + addr, buffer, count);
~~~~~~~~~~ ^
include/asm-generic/io.h:726:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
writesw(PCI_IOBASE + addr, buffer, count);
~~~~~~~~~~ ^
include/asm-generic/io.h:735:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
writesl(PCI_IOBASE + addr, buffer, count);
~~~~~~~~~~ ^
>> drivers/soc/qcom/ramp_controller.c:104:8: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
ack = FIELD_PREP(RC_CFG_ACK, BIT(ce));
^
12 warnings and 1 error generated.
vim +/FIELD_PREP +104 drivers/soc/qcom/ramp_controller.c
88
89 /**
90 * rc_set_cfg_update() - Ramp Controller configuration update
91 * @qrc: Main driver structure
92 * @ce: Configuration entry to update
93 *
94 * Return: Zero for success or negative number for failure
95 */
96 static int rc_set_cfg_update(struct qcom_ramp_controller *qrc, u8 ce)
97 {
98 const struct qcom_ramp_controller_desc *d = qrc->desc;
99 struct regmap *r = qrc->regmap;
100 u32 ack, val;
101 int ret;
102
103 /* The ack bit is between bits 16-31 of RC_REG_CFG_UPDATE */
> 104 ack = FIELD_PREP(RC_CFG_ACK, BIT(ce));
105
106 /* Write the configuration type first... */
107 ret = regmap_set_bits(r, d->cmd_reg + RC_REG_CFG_UPDATE, ce);
108 if (ret)
109 return ret;
110
111 /* ...and after that, enable the update bit to sync the changes */
112 ret = regmap_set_bits(r, d->cmd_reg + RC_REG_CFG_UPDATE, RC_CFG_UPDATE_EN);
113 if (ret)
114 return ret;
115
116 /* Wait for the changes to go through */
117 ret = regmap_read_poll_timeout(r, d->cmd_reg + RC_REG_CFG_UPDATE, val,
118 val & ack, 1, RC_UPDATE_TIMEOUT_US);
119 if (ret)
120 return ret;
121
122 /*
123 * Configuration update success! The CFG_UPDATE register will not be
124 * cleared automatically upon applying the configuration, so we have
125 * to do that manually in order to leave the ramp controller in a
126 * predictable and clean state.
127 */
128 ret = regmap_write(r, d->cmd_reg + RC_REG_CFG_UPDATE, 0);
129 if (ret)
130 return ret;
131
132 /* Wait for the update bit cleared ack */
133 return regmap_read_poll_timeout(r, d->cmd_reg + RC_REG_CFG_UPDATE,
134 val, !(val & RC_CFG_ACK), 1,
135 RC_UPDATE_TIMEOUT_US);
136 }
137
Il 16/11/22 14:46, Dmitry Baryshkov ha scritto:
> On 15/11/2022 18:45, AngeloGioacchino Del Regno wrote:
>> The Ramp Controller is used to program the sequence ID for pulse
>> swallowing, enable sequence and linking sequence IDs for the CPU
>> cores on some Qualcomm SoCs.
>>
>> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
>> ---
>> drivers/soc/qcom/Kconfig | 9 +
>> drivers/soc/qcom/Makefile | 1 +
>> drivers/soc/qcom/ramp_controller.c | 331 +++++++++++++++++++++++++++++
>> 3 files changed, 341 insertions(+)
>> create mode 100644 drivers/soc/qcom/ramp_controller.c
>>
>
> I'd allow myself to re-ask the same questions here:
>
> Generic question. regarding this controller. If it is supposed to work
> close to DVCS, etc. Does ramp controller need any programming when
> changing speed and/or APC voltage?
> Is it necessary to turn ramp on and off during the runtime?
>
No, it's a set-and-forget type of programming. Turning that on/off during
runtime is self-managed and, after the initial setting that this driver,
no further action is required - or at least not from Linux.
Cheers!
Angelo
On Thu, 17 Nov 2022 at 11:12, AngeloGioacchino Del Regno
<angelogioacchino.delregno@collabora.com> wrote:
>
> Il 16/11/22 14:46, Dmitry Baryshkov ha scritto:
> > On 15/11/2022 18:45, AngeloGioacchino Del Regno wrote:
> >> The Ramp Controller is used to program the sequence ID for pulse
> >> swallowing, enable sequence and linking sequence IDs for the CPU
> >> cores on some Qualcomm SoCs.
> >>
> >> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
> >> ---
> >> drivers/soc/qcom/Kconfig | 9 +
> >> drivers/soc/qcom/Makefile | 1 +
> >> drivers/soc/qcom/ramp_controller.c | 331 +++++++++++++++++++++++++++++
> >> 3 files changed, 341 insertions(+)
> >> create mode 100644 drivers/soc/qcom/ramp_controller.c
> >>
> >
> > I'd allow myself to re-ask the same questions here:
> >
> > Generic question. regarding this controller. If it is supposed to work
> > close to DVCS, etc. Does ramp controller need any programming when
> > changing speed and/or APC voltage?
> > Is it necessary to turn ramp on and off during the runtime?
> >
>
> No, it's a set-and-forget type of programming. Turning that on/off during
> runtime is self-managed and, after the initial setting that this driver,
> no further action is required - or at least not from Linux.
Ack, thank you!
On 15/11/2022 18:45, AngeloGioacchino Del Regno wrote:
> The Ramp Controller is used to program the sequence ID for pulse
> swallowing, enable sequence and linking sequence IDs for the CPU
> cores on some Qualcomm SoCs.
>
> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
> ---
> drivers/soc/qcom/Kconfig | 9 +
> drivers/soc/qcom/Makefile | 1 +
> drivers/soc/qcom/ramp_controller.c | 331 +++++++++++++++++++++++++++++
> 3 files changed, 341 insertions(+)
> create mode 100644 drivers/soc/qcom/ramp_controller.c
>
> diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
> index 024e420f1bb7..d174183a26f7 100644
> --- a/drivers/soc/qcom/Kconfig
> +++ b/drivers/soc/qcom/Kconfig
> @@ -95,6 +95,15 @@ config QCOM_QMI_HELPERS
> tristate
> depends on NET
>
> +config QCOM_RAMP_CTRL
> + tristate "Qualcomm Ramp Controller driver"
> + depends on ARCH_QCOM || COMPILE_TEST
> + help
> + The Ramp Controller is used to program the sequence ID for pulse
> + swallowing, enable sequence and link sequence IDs for the CPU
> + cores on some Qualcomm SoCs.
> + Say y here to enable support for the ramp controller.
> +
> config QCOM_RMTFS_MEM
> tristate "Qualcomm Remote Filesystem memory driver"
> depends on ARCH_QCOM
> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
> index d66604aff2b0..6e02333c4080 100644
> --- a/drivers/soc/qcom/Makefile
> +++ b/drivers/soc/qcom/Makefile
> @@ -10,6 +10,7 @@ obj-$(CONFIG_QCOM_OCMEM) += ocmem.o
> obj-$(CONFIG_QCOM_PDR_HELPERS) += pdr_interface.o
> obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o
> qmi_helpers-y += qmi_encdec.o qmi_interface.o
> +obj-$(CONFIG_QCOM_RAMP_CTRL) += ramp_controller.o
> obj-$(CONFIG_QCOM_RMTFS_MEM) += rmtfs_mem.o
> obj-$(CONFIG_QCOM_RPMH) += qcom_rpmh.o
> qcom_rpmh-y += rpmh-rsc.o
> diff --git a/drivers/soc/qcom/ramp_controller.c b/drivers/soc/qcom/ramp_controller.c
> new file mode 100644
> index 000000000000..b403493f3541
> --- /dev/null
> +++ b/drivers/soc/qcom/ramp_controller.c
> @@ -0,0 +1,331 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Qualcomm Ramp Controller driver
> + * Copyright (c) 2022, AngeloGioacchino Del Regno
> + * <angelogioacchino.delregno@collabora.com>
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/types.h>
> +
> +#define RC_UPDATE_EN BIT(0)
> +#define RC_ROOT_EN BIT(1)
> +
> +#define RC_REG_CFG_UPDATE 0x60
> +#define RC_CFG_UPDATE_EN BIT(8)
> +#define RC_CFG_ACK GENMASK(31, 16)
> +
> +#define RC_DCVS_CFG_SID 2
> +#define RC_LINK_SID 3
> +#define RC_LMH_SID 6
> +#define RC_DFS_SID 14
> +
> +#define RC_UPDATE_TIMEOUT_US 500
> +
> +/**
> + * struct qcom_ramp_controller_desc - SoC specific parameters
> + * @cfg_dfs_sid: Dynamic Frequency Scaling SID configuration
> + * @cfg_link_sid: Link SID configuration
> + * @cfg_lmh_sid: Limits Management hardware SID configuration
> + * @cfg_ramp_pre_en: Ramp Controller pre-enable sequence
> + * @cfg_ramp_en: Ramp Controller enable sequence
> + * @cfg_ramp_post_en: Ramp Controller post-enable sequence
> + * @cfg_ramp_dis: Ramp Controller disable sequence
> + * @cmd_reg: Command register offset
> + * @num_dfs_sids: Number of DFS SIDs (max 8)
> + * @num_link_sids: Number of Link SIDs (max 3)
> + * @num_lmh_sids: Number of LMh SIDs (max 8)
> + */
> +struct qcom_ramp_controller_desc {
> + const struct reg_sequence *cfg_dfs_sid;
> + const struct reg_sequence *cfg_link_sid;
> + const struct reg_sequence *cfg_lmh_sid;
> + const struct reg_sequence *cfg_ramp_pre_en;
> + const struct reg_sequence *cfg_ramp_en;
> + const struct reg_sequence *cfg_ramp_post_en;
> + const struct reg_sequence *cfg_ramp_dis;
> + u8 cmd_reg;
> + u8 num_dfs_sids;
> + u8 num_link_sids;
> + u8 num_lmh_sids;
> +};
> +
> +/**
> + * struct qcom_ramp_controller - Main driver structure
> + * @regmap: Regmap handle
> + * @desc: SoC specific parameters
> + */
> +struct qcom_ramp_controller {
> + struct regmap *regmap;
> + const struct qcom_ramp_controller_desc *desc;
> +};
> +
> +/**
> + * rc_wait_for_update() - Wait for Ramp Controller root update
> + * @qrc: Main driver structure
> + *
> + * Return: Zero for success or negative number for failure
> + */
> +static int rc_wait_for_update(struct qcom_ramp_controller *qrc)
> +{
> + const struct qcom_ramp_controller_desc *d = qrc->desc;
> + struct regmap *r = qrc->regmap;
> + u32 val;
> + int ret;
> +
> + ret = regmap_set_bits(r, d->cmd_reg, RC_ROOT_EN);
> + if (ret)
> + return ret;
> +
> + return regmap_read_poll_timeout(r, d->cmd_reg, val, !(val & RC_UPDATE_EN),
> + 1, RC_UPDATE_TIMEOUT_US);
> +}
> +
> +/**
> + * rc_set_cfg_update() - Ramp Controller configuration update
> + * @qrc: Main driver structure
> + * @ce: Configuration entry to update
> + *
> + * Return: Zero for success or negative number for failure
> + */
> +static int rc_set_cfg_update(struct qcom_ramp_controller *qrc, u8 ce)
> +{
> + const struct qcom_ramp_controller_desc *d = qrc->desc;
> + struct regmap *r = qrc->regmap;
> + u32 ack, val;
> + int ret;
> +
> + /* The ack bit is between bits 16-31 of RC_REG_CFG_UPDATE */
> + ack = FIELD_PREP(RC_CFG_ACK, BIT(ce));
> +
> + /* Write the configuration type first... */
> + ret = regmap_set_bits(r, d->cmd_reg + RC_REG_CFG_UPDATE, ce);
> + if (ret)
> + return ret;
> +
> + /* ...and after that, enable the update bit to sync the changes */
> + ret = regmap_set_bits(r, d->cmd_reg + RC_REG_CFG_UPDATE, RC_CFG_UPDATE_EN);
> + if (ret)
> + return ret;
> +
> + /* Wait for the changes to go through */
> + ret = regmap_read_poll_timeout(r, d->cmd_reg + RC_REG_CFG_UPDATE, val,
> + val & ack, 1, RC_UPDATE_TIMEOUT_US);
> + if (ret)
> + return ret;
> +
> + /*
> + * Configuration update success! The CFG_UPDATE register will not be
> + * cleared automatically upon applying the configuration, so we have
> + * to do that manually in order to leave the ramp controller in a
> + * predictable and clean state.
> + */
> + ret = regmap_write(r, d->cmd_reg + RC_REG_CFG_UPDATE, 0);
> + if (ret)
> + return ret;
> +
> + /* Wait for the update bit cleared ack */
> + return regmap_read_poll_timeout(r, d->cmd_reg + RC_REG_CFG_UPDATE,
> + val, !(val & RC_CFG_ACK), 1,
> + RC_UPDATE_TIMEOUT_US);
> +}
> +
> +/**
> + * rc_write_cfg - Send configuration sequence
> + * @qrc: Main driver structure
> + * @seq: Register sequence to send before asking for update
> + * @ce: Configuration SID
> + * @nsids: Total number of SIDs
> + *
> + * Returns: Zero for success or negative number for error
> + */
> +static int rc_write_cfg(struct qcom_ramp_controller *qrc,
> + const struct reg_sequence *seq,
> + u16 ce, u8 nsids)
> +{
> + int ret;
> + u8 i;
> +
> + /* Check if, and wait until the ramp controller is ready */
> + ret = rc_wait_for_update(qrc);
> + if (ret)
> + return ret;
> +
> + /* Write the sequence */
> + ret = regmap_multi_reg_write(qrc->regmap, seq, nsids);
> + if (ret)
> + return ret;
> +
> + /* Pull the trigger: do config update starting from the last sid */
> + for (i = 0; i < nsids; i++) {
> + ret = rc_set_cfg_update(qrc, (u8)ce - i);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * rc_ramp_ctrl_enable() - Enable Ramp up/down Control
> + * @qrc: Main driver structure
> + *
> + * Return: Zero for success or negative number for error
> + */
> +static int rc_ramp_ctrl_enable(struct qcom_ramp_controller *qrc)
> +{
> + const struct qcom_ramp_controller_desc *d = qrc->desc;
> + int ret;
> +
> + ret = rc_write_cfg(qrc, d->cfg_ramp_pre_en, RC_DCVS_CFG_SID, 1);
ARRAY_SIZE or num_somthing (here and below)
> + if (ret)
> + return ret;
> +
> + ret = rc_write_cfg(qrc, d->cfg_ramp_en, RC_DCVS_CFG_SID, 1);
> + if (ret)
> + return ret;
> +
> + return rc_write_cfg(qrc, d->cfg_ramp_post_en, RC_DCVS_CFG_SID, 1);
Is there any benefit from having separate pre_en, en and post_en configs
here? I'd suggest using a single array in the configuration.
> +}
> +
> +/**
> + * qcom_ramp_controller_start() - Initialize and start the ramp controller
> + * @qrc: Main driver structure
> + *
> + * The Ramp Controller needs to be initialized by programming the relevant
> + * registers with SoC-specific configuration: once programming is done,
> + * the hardware will take care of the rest (no further handling required).
> + *
> + * Return: Zero for success or negative number for error
> + */
> +static int qcom_ramp_controller_start(struct qcom_ramp_controller *qrc)
> +{
> + const struct qcom_ramp_controller_desc *d = qrc->desc;
> + int ret;
> +
> + /* Program LMH, DFS, Link SIDs */
> + ret = rc_write_cfg(qrc, d->cfg_lmh_sid, RC_LMH_SID, d->num_lmh_sids);
> + if (ret)
> + return ret;
> +
> + ret = rc_write_cfg(qrc, d->cfg_dfs_sid, RC_DFS_SID, d->num_dfs_sids);
> + if (ret)
> + return ret;
> +
> + ret = rc_write_cfg(qrc, d->cfg_link_sid, RC_LINK_SID, d->num_link_sids);
> + if (ret)
> + return ret;
> +
> + /* Everything is ready! Enable the ramp up/down control */
> + return rc_ramp_ctrl_enable(qrc);
> +}
> +
> +static const struct regmap_config qrc_regmap_config = {
> + .reg_bits = 32,
> + .reg_stride = 4,
> + .val_bits = 32,
> + .max_register = 0x68,
> + .fast_io = true,
> +};
> +
> +static const struct qcom_ramp_controller_desc msm8976_rc_cfg = {
> + .cfg_dfs_sid = (const struct reg_sequence[]) {
> + { 0x10, 0xfefebff7 },
> + { 0x14, 0xfdff7fef },
> + { 0x18, 0xfbffdefb },
> + { 0x1c, 0xb69b5555 },
> + { 0x20, 0x24929249 },
> + { 0x24, 0x49241112 },
> + { 0x28, 0x11112111 },
> + { 0x2c, 0x8102 },
> + },
Please move these config arrays to the named constants. This would allow
you to use ARRAY_SIZE below, for num_foo_sids variables.
> + .cfg_link_sid = (const struct reg_sequence[]) {
> + { 0x40, 0xfc987 },
> + },
> + .cfg_lmh_sid = (const struct reg_sequence[]) {
> + { 0x30, 0x77706db },
> + { 0x34, 0x5550249 },
> + { 0x38, 0x111 },
> + },
> + .cfg_ramp_pre_en = (const struct reg_sequence[]) {
> + { 0x50, 0x800 },
> + },
> + .cfg_ramp_en = (const struct reg_sequence[]) {
> + { 0x50, 0xc00 },
> + },
> + .cfg_ramp_post_en = (const struct reg_sequence[]) {
> + { 0x50, 0x400 },
> + },
> + .cfg_ramp_dis = (const struct reg_sequence[]) {
> + { 0x50, 0x0 },
> + },
> + .cmd_reg = 0x0,
> +
> + .num_dfs_sids = 8,
> + .num_lmh_sids = 3,
> + .num_link_sids = 1,
> +};
> +
> +static int qcom_ramp_controller_probe(struct platform_device *pdev)
> +{
> + struct qcom_ramp_controller *qrc;
> + void __iomem *base;
> +
> + base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(base))
> + return PTR_ERR(base);
> +
> + qrc = devm_kmalloc(&pdev->dev, sizeof(*qrc), GFP_KERNEL);
> + if (!qrc)
> + return -ENOMEM;
> +
> + qrc->desc = device_get_match_data(&pdev->dev);
> + if (!qrc)
> + return -EINVAL;
> +
> + qrc->regmap = devm_regmap_init_mmio(&pdev->dev, base, &qrc_regmap_config);
> + if (IS_ERR(qrc->regmap))
> + return PTR_ERR(qrc->regmap);
> +
> + platform_set_drvdata(pdev, qrc);
> +
> + return qcom_ramp_controller_start(qrc);
> +}
> +
> +static int qcom_ramp_controller_remove(struct platform_device *pdev)
> +{
> + struct qcom_ramp_controller *qrc = platform_get_drvdata(pdev);
> +
> + return rc_write_cfg(qrc, qrc->desc->cfg_ramp_dis, RC_DCVS_CFG_SID, 1);
ARRAY_SIZE or num_something
> +}
> +
> +static const struct of_device_id qcom_ramp_controller_match_table[] = {
> + { .compatible = "qcom,msm8976-ramp-controller", .data = &msm8976_rc_cfg },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, qcom_ramp_controller_match_table);
> +
> +static struct platform_driver qcom_ramp_controller_driver = {
> + .driver = {
> + .name = "qcom-ramp-controller",
> + .of_match_table = qcom_ramp_controller_match_table,
> + .suppress_bind_attrs = true,
> + },
> + .probe = qcom_ramp_controller_probe,
> + .remove = qcom_ramp_controller_remove,
> +};
> +
> +static int __init qcom_ramp_controller_init(void)
> +{
> + return platform_driver_register(&qcom_ramp_controller_driver);
> +}
> +arch_initcall(qcom_ramp_controller_init);
> +
> +MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>");
> +MODULE_DESCRIPTION("Qualcomm Ramp Controller driver");
> +MODULE_LICENSE("GPL");
Il 17/11/22 13:05, Dmitry Baryshkov ha scritto:
> On 15/11/2022 18:45, AngeloGioacchino Del Regno wrote:
>> The Ramp Controller is used to program the sequence ID for pulse
>> swallowing, enable sequence and linking sequence IDs for the CPU
>> cores on some Qualcomm SoCs.
>>
>> Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
>> ---
>> drivers/soc/qcom/Kconfig | 9 +
>> drivers/soc/qcom/Makefile | 1 +
>> drivers/soc/qcom/ramp_controller.c | 331 +++++++++++++++++++++++++++++
>> 3 files changed, 341 insertions(+)
>> create mode 100644 drivers/soc/qcom/ramp_controller.c
>>
>> diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
>> index 024e420f1bb7..d174183a26f7 100644
>> --- a/drivers/soc/qcom/Kconfig
>> +++ b/drivers/soc/qcom/Kconfig
>> @@ -95,6 +95,15 @@ config QCOM_QMI_HELPERS
>> tristate
>> depends on NET
>> +config QCOM_RAMP_CTRL
>> + tristate "Qualcomm Ramp Controller driver"
>> + depends on ARCH_QCOM || COMPILE_TEST
>> + help
>> + The Ramp Controller is used to program the sequence ID for pulse
>> + swallowing, enable sequence and link sequence IDs for the CPU
>> + cores on some Qualcomm SoCs.
>> + Say y here to enable support for the ramp controller.
>> +
>> config QCOM_RMTFS_MEM
>> tristate "Qualcomm Remote Filesystem memory driver"
>> depends on ARCH_QCOM
>> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
>> index d66604aff2b0..6e02333c4080 100644
>> --- a/drivers/soc/qcom/Makefile
>> +++ b/drivers/soc/qcom/Makefile
>> @@ -10,6 +10,7 @@ obj-$(CONFIG_QCOM_OCMEM) += ocmem.o
>> obj-$(CONFIG_QCOM_PDR_HELPERS) += pdr_interface.o
>> obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o
>> qmi_helpers-y += qmi_encdec.o qmi_interface.o
>> +obj-$(CONFIG_QCOM_RAMP_CTRL) += ramp_controller.o
>> obj-$(CONFIG_QCOM_RMTFS_MEM) += rmtfs_mem.o
>> obj-$(CONFIG_QCOM_RPMH) += qcom_rpmh.o
>> qcom_rpmh-y += rpmh-rsc.o
>> diff --git a/drivers/soc/qcom/ramp_controller.c b/drivers/soc/qcom/ramp_controller.c
>> new file mode 100644
>> index 000000000000..b403493f3541
>> --- /dev/null
>> +++ b/drivers/soc/qcom/ramp_controller.c
>> @@ -0,0 +1,331 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Qualcomm Ramp Controller driver
>> + * Copyright (c) 2022, AngeloGioacchino Del Regno
>> + * <angelogioacchino.delregno@collabora.com>
>> + */
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/regmap.h>
>> +#include <linux/types.h>
>> +
>> +#define RC_UPDATE_EN BIT(0)
>> +#define RC_ROOT_EN BIT(1)
>> +
>> +#define RC_REG_CFG_UPDATE 0x60
>> +#define RC_CFG_UPDATE_EN BIT(8)
>> +#define RC_CFG_ACK GENMASK(31, 16)
>> +
>> +#define RC_DCVS_CFG_SID 2
>> +#define RC_LINK_SID 3
>> +#define RC_LMH_SID 6
>> +#define RC_DFS_SID 14
>> +
>> +#define RC_UPDATE_TIMEOUT_US 500
>> +
>> +/**
>> + * struct qcom_ramp_controller_desc - SoC specific parameters
>> + * @cfg_dfs_sid: Dynamic Frequency Scaling SID configuration
>> + * @cfg_link_sid: Link SID configuration
>> + * @cfg_lmh_sid: Limits Management hardware SID configuration
>> + * @cfg_ramp_pre_en: Ramp Controller pre-enable sequence
>> + * @cfg_ramp_en: Ramp Controller enable sequence
>> + * @cfg_ramp_post_en: Ramp Controller post-enable sequence
>> + * @cfg_ramp_dis: Ramp Controller disable sequence
>> + * @cmd_reg: Command register offset
>> + * @num_dfs_sids: Number of DFS SIDs (max 8)
>> + * @num_link_sids: Number of Link SIDs (max 3)
>> + * @num_lmh_sids: Number of LMh SIDs (max 8)
>> + */
>> +struct qcom_ramp_controller_desc {
>> + const struct reg_sequence *cfg_dfs_sid;
>> + const struct reg_sequence *cfg_link_sid;
>> + const struct reg_sequence *cfg_lmh_sid;
>> + const struct reg_sequence *cfg_ramp_pre_en;
>> + const struct reg_sequence *cfg_ramp_en;
>> + const struct reg_sequence *cfg_ramp_post_en;
>> + const struct reg_sequence *cfg_ramp_dis;
>> + u8 cmd_reg;
>> + u8 num_dfs_sids;
>> + u8 num_link_sids;
>> + u8 num_lmh_sids;
>> +};
>> +
>> +/**
>> + * struct qcom_ramp_controller - Main driver structure
>> + * @regmap: Regmap handle
>> + * @desc: SoC specific parameters
>> + */
>> +struct qcom_ramp_controller {
>> + struct regmap *regmap;
>> + const struct qcom_ramp_controller_desc *desc;
>> +};
>> +
>> +/**
>> + * rc_wait_for_update() - Wait for Ramp Controller root update
>> + * @qrc: Main driver structure
>> + *
>> + * Return: Zero for success or negative number for failure
>> + */
>> +static int rc_wait_for_update(struct qcom_ramp_controller *qrc)
>> +{
>> + const struct qcom_ramp_controller_desc *d = qrc->desc;
>> + struct regmap *r = qrc->regmap;
>> + u32 val;
>> + int ret;
>> +
>> + ret = regmap_set_bits(r, d->cmd_reg, RC_ROOT_EN);
>> + if (ret)
>> + return ret;
>> +
>> + return regmap_read_poll_timeout(r, d->cmd_reg, val, !(val & RC_UPDATE_EN),
>> + 1, RC_UPDATE_TIMEOUT_US);
>> +}
>> +
>> +/**
>> + * rc_set_cfg_update() - Ramp Controller configuration update
>> + * @qrc: Main driver structure
>> + * @ce: Configuration entry to update
>> + *
>> + * Return: Zero for success or negative number for failure
>> + */
>> +static int rc_set_cfg_update(struct qcom_ramp_controller *qrc, u8 ce)
>> +{
>> + const struct qcom_ramp_controller_desc *d = qrc->desc;
>> + struct regmap *r = qrc->regmap;
>> + u32 ack, val;
>> + int ret;
>> +
>> + /* The ack bit is between bits 16-31 of RC_REG_CFG_UPDATE */
>> + ack = FIELD_PREP(RC_CFG_ACK, BIT(ce));
>> +
>> + /* Write the configuration type first... */
>> + ret = regmap_set_bits(r, d->cmd_reg + RC_REG_CFG_UPDATE, ce);
>> + if (ret)
>> + return ret;
>> +
>> + /* ...and after that, enable the update bit to sync the changes */
>> + ret = regmap_set_bits(r, d->cmd_reg + RC_REG_CFG_UPDATE, RC_CFG_UPDATE_EN);
>> + if (ret)
>> + return ret;
>> +
>> + /* Wait for the changes to go through */
>> + ret = regmap_read_poll_timeout(r, d->cmd_reg + RC_REG_CFG_UPDATE, val,
>> + val & ack, 1, RC_UPDATE_TIMEOUT_US);
>> + if (ret)
>> + return ret;
>> +
>> + /*
>> + * Configuration update success! The CFG_UPDATE register will not be
>> + * cleared automatically upon applying the configuration, so we have
>> + * to do that manually in order to leave the ramp controller in a
>> + * predictable and clean state.
>> + */
>> + ret = regmap_write(r, d->cmd_reg + RC_REG_CFG_UPDATE, 0);
>> + if (ret)
>> + return ret;
>> +
>> + /* Wait for the update bit cleared ack */
>> + return regmap_read_poll_timeout(r, d->cmd_reg + RC_REG_CFG_UPDATE,
>> + val, !(val & RC_CFG_ACK), 1,
>> + RC_UPDATE_TIMEOUT_US);
>> +}
>> +
>> +/**
>> + * rc_write_cfg - Send configuration sequence
>> + * @qrc: Main driver structure
>> + * @seq: Register sequence to send before asking for update
>> + * @ce: Configuration SID
>> + * @nsids: Total number of SIDs
>> + *
>> + * Returns: Zero for success or negative number for error
>> + */
>> +static int rc_write_cfg(struct qcom_ramp_controller *qrc,
>> + const struct reg_sequence *seq,
>> + u16 ce, u8 nsids)
>> +{
>> + int ret;
>> + u8 i;
>> +
>> + /* Check if, and wait until the ramp controller is ready */
>> + ret = rc_wait_for_update(qrc);
>> + if (ret)
>> + return ret;
>> +
>> + /* Write the sequence */
>> + ret = regmap_multi_reg_write(qrc->regmap, seq, nsids);
>> + if (ret)
>> + return ret;
>> +
>> + /* Pull the trigger: do config update starting from the last sid */
>> + for (i = 0; i < nsids; i++) {
>> + ret = rc_set_cfg_update(qrc, (u8)ce - i);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +/**
>> + * rc_ramp_ctrl_enable() - Enable Ramp up/down Control
>> + * @qrc: Main driver structure
>> + *
>> + * Return: Zero for success or negative number for error
>> + */
>> +static int rc_ramp_ctrl_enable(struct qcom_ramp_controller *qrc)
>> +{
>> + const struct qcom_ramp_controller_desc *d = qrc->desc;
>> + int ret;
>> +
>> + ret = rc_write_cfg(qrc, d->cfg_ramp_pre_en, RC_DCVS_CFG_SID, 1);
>
> ARRAY_SIZE or num_somthing (here and below)
>
Done in v4.
>> + if (ret)
>> + return ret;
>> +
>> + ret = rc_write_cfg(qrc, d->cfg_ramp_en, RC_DCVS_CFG_SID, 1);
>> + if (ret)
>> + return ret;
>> +
>> + return rc_write_cfg(qrc, d->cfg_ramp_post_en, RC_DCVS_CFG_SID, 1);
>
> Is there any benefit from having separate pre_en, en and post_en configs here? I'd
> suggest using a single array in the configuration.
>
Just documenting what the commands are and improving readability instead of
using a for loop to call rc_write_cfg() multiple times, once for each
command in cfg_ramp_en[] - as for each of these we have to wait for the
execution of the previous command, send the new one, commit to controller.
Whatever, I'll change this to use one reg_sequence array and a for loop.
>> +}
>> +
>> +/**
>> + * qcom_ramp_controller_start() - Initialize and start the ramp controller
>> + * @qrc: Main driver structure
>> + *
>> + * The Ramp Controller needs to be initialized by programming the relevant
>> + * registers with SoC-specific configuration: once programming is done,
>> + * the hardware will take care of the rest (no further handling required).
>> + *
>> + * Return: Zero for success or negative number for error
>> + */
>> +static int qcom_ramp_controller_start(struct qcom_ramp_controller *qrc)
>> +{
>> + const struct qcom_ramp_controller_desc *d = qrc->desc;
>> + int ret;
>> +
>> + /* Program LMH, DFS, Link SIDs */
>> + ret = rc_write_cfg(qrc, d->cfg_lmh_sid, RC_LMH_SID, d->num_lmh_sids);
>> + if (ret)
>> + return ret;
>> +
>> + ret = rc_write_cfg(qrc, d->cfg_dfs_sid, RC_DFS_SID, d->num_dfs_sids);
>> + if (ret)
>> + return ret;
>> +
>> + ret = rc_write_cfg(qrc, d->cfg_link_sid, RC_LINK_SID, d->num_link_sids);
>> + if (ret)
>> + return ret;
>> +
>> + /* Everything is ready! Enable the ramp up/down control */
>> + return rc_ramp_ctrl_enable(qrc);
>> +}
>> +
>> +static const struct regmap_config qrc_regmap_config = {
>> + .reg_bits = 32,
>> + .reg_stride = 4,
>> + .val_bits = 32,
>> + .max_register = 0x68,
>> + .fast_io = true,
>> +};
>> +
>> +static const struct qcom_ramp_controller_desc msm8976_rc_cfg = {
>> + .cfg_dfs_sid = (const struct reg_sequence[]) {
>> + { 0x10, 0xfefebff7 },
>> + { 0x14, 0xfdff7fef },
>> + { 0x18, 0xfbffdefb },
>> + { 0x1c, 0xb69b5555 },
>> + { 0x20, 0x24929249 },
>> + { 0x24, 0x49241112 },
>> + { 0x28, 0x11112111 },
>> + { 0x2c, 0x8102 },
>> + },
>
> Please move these config arrays to the named constants. This would allow you to use
> ARRAY_SIZE below, for num_foo_sids variables.
>
Done in v4.
>> + .cfg_link_sid = (const struct reg_sequence[]) {
>> + { 0x40, 0xfc987 },
>> + },
>> + .cfg_lmh_sid = (const struct reg_sequence[]) {
>> + { 0x30, 0x77706db },
>> + { 0x34, 0x5550249 },
>> + { 0x38, 0x111 },
>> + },
>> + .cfg_ramp_pre_en = (const struct reg_sequence[]) {
>> + { 0x50, 0x800 },
>> + },
>> + .cfg_ramp_en = (const struct reg_sequence[]) {
>> + { 0x50, 0xc00 },
>> + },
>> + .cfg_ramp_post_en = (const struct reg_sequence[]) {
>> + { 0x50, 0x400 },
>> + },
>> + .cfg_ramp_dis = (const struct reg_sequence[]) {
>> + { 0x50, 0x0 },
>> + },
>> + .cmd_reg = 0x0,
>> +
>> + .num_dfs_sids = 8,
>> + .num_lmh_sids = 3,
>> + .num_link_sids = 1,
>> +};
>> +
>> +static int qcom_ramp_controller_probe(struct platform_device *pdev)
>> +{
>> + struct qcom_ramp_controller *qrc;
>> + void __iomem *base;
>> +
>> + base = devm_platform_ioremap_resource(pdev, 0);
>> + if (IS_ERR(base))
>> + return PTR_ERR(base);
>> +
>> + qrc = devm_kmalloc(&pdev->dev, sizeof(*qrc), GFP_KERNEL);
>> + if (!qrc)
>> + return -ENOMEM;
>> +
>> + qrc->desc = device_get_match_data(&pdev->dev);
>> + if (!qrc)
>> + return -EINVAL;
>> +
>> + qrc->regmap = devm_regmap_init_mmio(&pdev->dev, base, &qrc_regmap_config);
>> + if (IS_ERR(qrc->regmap))
>> + return PTR_ERR(qrc->regmap);
>> +
>> + platform_set_drvdata(pdev, qrc);
>> +
>> + return qcom_ramp_controller_start(qrc);
>> +}
>> +
>> +static int qcom_ramp_controller_remove(struct platform_device *pdev)
>> +{
>> + struct qcom_ramp_controller *qrc = platform_get_drvdata(pdev);
>> +
>> + return rc_write_cfg(qrc, qrc->desc->cfg_ramp_dis, RC_DCVS_CFG_SID, 1);
>
> ARRAY_SIZE or num_something
>
Done in v4.
Regards,
Angelo
@@ -95,6 +95,15 @@ config QCOM_QMI_HELPERS
tristate
depends on NET
+config QCOM_RAMP_CTRL
+ tristate "Qualcomm Ramp Controller driver"
+ depends on ARCH_QCOM || COMPILE_TEST
+ help
+ The Ramp Controller is used to program the sequence ID for pulse
+ swallowing, enable sequence and link sequence IDs for the CPU
+ cores on some Qualcomm SoCs.
+ Say y here to enable support for the ramp controller.
+
config QCOM_RMTFS_MEM
tristate "Qualcomm Remote Filesystem memory driver"
depends on ARCH_QCOM
@@ -10,6 +10,7 @@ obj-$(CONFIG_QCOM_OCMEM) += ocmem.o
obj-$(CONFIG_QCOM_PDR_HELPERS) += pdr_interface.o
obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o
qmi_helpers-y += qmi_encdec.o qmi_interface.o
+obj-$(CONFIG_QCOM_RAMP_CTRL) += ramp_controller.o
obj-$(CONFIG_QCOM_RMTFS_MEM) += rmtfs_mem.o
obj-$(CONFIG_QCOM_RPMH) += qcom_rpmh.o
qcom_rpmh-y += rpmh-rsc.o
new file mode 100644
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Qualcomm Ramp Controller driver
+ * Copyright (c) 2022, AngeloGioacchino Del Regno
+ * <angelogioacchino.delregno@collabora.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define RC_UPDATE_EN BIT(0)
+#define RC_ROOT_EN BIT(1)
+
+#define RC_REG_CFG_UPDATE 0x60
+#define RC_CFG_UPDATE_EN BIT(8)
+#define RC_CFG_ACK GENMASK(31, 16)
+
+#define RC_DCVS_CFG_SID 2
+#define RC_LINK_SID 3
+#define RC_LMH_SID 6
+#define RC_DFS_SID 14
+
+#define RC_UPDATE_TIMEOUT_US 500
+
+/**
+ * struct qcom_ramp_controller_desc - SoC specific parameters
+ * @cfg_dfs_sid: Dynamic Frequency Scaling SID configuration
+ * @cfg_link_sid: Link SID configuration
+ * @cfg_lmh_sid: Limits Management hardware SID configuration
+ * @cfg_ramp_pre_en: Ramp Controller pre-enable sequence
+ * @cfg_ramp_en: Ramp Controller enable sequence
+ * @cfg_ramp_post_en: Ramp Controller post-enable sequence
+ * @cfg_ramp_dis: Ramp Controller disable sequence
+ * @cmd_reg: Command register offset
+ * @num_dfs_sids: Number of DFS SIDs (max 8)
+ * @num_link_sids: Number of Link SIDs (max 3)
+ * @num_lmh_sids: Number of LMh SIDs (max 8)
+ */
+struct qcom_ramp_controller_desc {
+ const struct reg_sequence *cfg_dfs_sid;
+ const struct reg_sequence *cfg_link_sid;
+ const struct reg_sequence *cfg_lmh_sid;
+ const struct reg_sequence *cfg_ramp_pre_en;
+ const struct reg_sequence *cfg_ramp_en;
+ const struct reg_sequence *cfg_ramp_post_en;
+ const struct reg_sequence *cfg_ramp_dis;
+ u8 cmd_reg;
+ u8 num_dfs_sids;
+ u8 num_link_sids;
+ u8 num_lmh_sids;
+};
+
+/**
+ * struct qcom_ramp_controller - Main driver structure
+ * @regmap: Regmap handle
+ * @desc: SoC specific parameters
+ */
+struct qcom_ramp_controller {
+ struct regmap *regmap;
+ const struct qcom_ramp_controller_desc *desc;
+};
+
+/**
+ * rc_wait_for_update() - Wait for Ramp Controller root update
+ * @qrc: Main driver structure
+ *
+ * Return: Zero for success or negative number for failure
+ */
+static int rc_wait_for_update(struct qcom_ramp_controller *qrc)
+{
+ const struct qcom_ramp_controller_desc *d = qrc->desc;
+ struct regmap *r = qrc->regmap;
+ u32 val;
+ int ret;
+
+ ret = regmap_set_bits(r, d->cmd_reg, RC_ROOT_EN);
+ if (ret)
+ return ret;
+
+ return regmap_read_poll_timeout(r, d->cmd_reg, val, !(val & RC_UPDATE_EN),
+ 1, RC_UPDATE_TIMEOUT_US);
+}
+
+/**
+ * rc_set_cfg_update() - Ramp Controller configuration update
+ * @qrc: Main driver structure
+ * @ce: Configuration entry to update
+ *
+ * Return: Zero for success or negative number for failure
+ */
+static int rc_set_cfg_update(struct qcom_ramp_controller *qrc, u8 ce)
+{
+ const struct qcom_ramp_controller_desc *d = qrc->desc;
+ struct regmap *r = qrc->regmap;
+ u32 ack, val;
+ int ret;
+
+ /* The ack bit is between bits 16-31 of RC_REG_CFG_UPDATE */
+ ack = FIELD_PREP(RC_CFG_ACK, BIT(ce));
+
+ /* Write the configuration type first... */
+ ret = regmap_set_bits(r, d->cmd_reg + RC_REG_CFG_UPDATE, ce);
+ if (ret)
+ return ret;
+
+ /* ...and after that, enable the update bit to sync the changes */
+ ret = regmap_set_bits(r, d->cmd_reg + RC_REG_CFG_UPDATE, RC_CFG_UPDATE_EN);
+ if (ret)
+ return ret;
+
+ /* Wait for the changes to go through */
+ ret = regmap_read_poll_timeout(r, d->cmd_reg + RC_REG_CFG_UPDATE, val,
+ val & ack, 1, RC_UPDATE_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ /*
+ * Configuration update success! The CFG_UPDATE register will not be
+ * cleared automatically upon applying the configuration, so we have
+ * to do that manually in order to leave the ramp controller in a
+ * predictable and clean state.
+ */
+ ret = regmap_write(r, d->cmd_reg + RC_REG_CFG_UPDATE, 0);
+ if (ret)
+ return ret;
+
+ /* Wait for the update bit cleared ack */
+ return regmap_read_poll_timeout(r, d->cmd_reg + RC_REG_CFG_UPDATE,
+ val, !(val & RC_CFG_ACK), 1,
+ RC_UPDATE_TIMEOUT_US);
+}
+
+/**
+ * rc_write_cfg - Send configuration sequence
+ * @qrc: Main driver structure
+ * @seq: Register sequence to send before asking for update
+ * @ce: Configuration SID
+ * @nsids: Total number of SIDs
+ *
+ * Returns: Zero for success or negative number for error
+ */
+static int rc_write_cfg(struct qcom_ramp_controller *qrc,
+ const struct reg_sequence *seq,
+ u16 ce, u8 nsids)
+{
+ int ret;
+ u8 i;
+
+ /* Check if, and wait until the ramp controller is ready */
+ ret = rc_wait_for_update(qrc);
+ if (ret)
+ return ret;
+
+ /* Write the sequence */
+ ret = regmap_multi_reg_write(qrc->regmap, seq, nsids);
+ if (ret)
+ return ret;
+
+ /* Pull the trigger: do config update starting from the last sid */
+ for (i = 0; i < nsids; i++) {
+ ret = rc_set_cfg_update(qrc, (u8)ce - i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * rc_ramp_ctrl_enable() - Enable Ramp up/down Control
+ * @qrc: Main driver structure
+ *
+ * Return: Zero for success or negative number for error
+ */
+static int rc_ramp_ctrl_enable(struct qcom_ramp_controller *qrc)
+{
+ const struct qcom_ramp_controller_desc *d = qrc->desc;
+ int ret;
+
+ ret = rc_write_cfg(qrc, d->cfg_ramp_pre_en, RC_DCVS_CFG_SID, 1);
+ if (ret)
+ return ret;
+
+ ret = rc_write_cfg(qrc, d->cfg_ramp_en, RC_DCVS_CFG_SID, 1);
+ if (ret)
+ return ret;
+
+ return rc_write_cfg(qrc, d->cfg_ramp_post_en, RC_DCVS_CFG_SID, 1);
+}
+
+/**
+ * qcom_ramp_controller_start() - Initialize and start the ramp controller
+ * @qrc: Main driver structure
+ *
+ * The Ramp Controller needs to be initialized by programming the relevant
+ * registers with SoC-specific configuration: once programming is done,
+ * the hardware will take care of the rest (no further handling required).
+ *
+ * Return: Zero for success or negative number for error
+ */
+static int qcom_ramp_controller_start(struct qcom_ramp_controller *qrc)
+{
+ const struct qcom_ramp_controller_desc *d = qrc->desc;
+ int ret;
+
+ /* Program LMH, DFS, Link SIDs */
+ ret = rc_write_cfg(qrc, d->cfg_lmh_sid, RC_LMH_SID, d->num_lmh_sids);
+ if (ret)
+ return ret;
+
+ ret = rc_write_cfg(qrc, d->cfg_dfs_sid, RC_DFS_SID, d->num_dfs_sids);
+ if (ret)
+ return ret;
+
+ ret = rc_write_cfg(qrc, d->cfg_link_sid, RC_LINK_SID, d->num_link_sids);
+ if (ret)
+ return ret;
+
+ /* Everything is ready! Enable the ramp up/down control */
+ return rc_ramp_ctrl_enable(qrc);
+}
+
+static const struct regmap_config qrc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x68,
+ .fast_io = true,
+};
+
+static const struct qcom_ramp_controller_desc msm8976_rc_cfg = {
+ .cfg_dfs_sid = (const struct reg_sequence[]) {
+ { 0x10, 0xfefebff7 },
+ { 0x14, 0xfdff7fef },
+ { 0x18, 0xfbffdefb },
+ { 0x1c, 0xb69b5555 },
+ { 0x20, 0x24929249 },
+ { 0x24, 0x49241112 },
+ { 0x28, 0x11112111 },
+ { 0x2c, 0x8102 },
+ },
+ .cfg_link_sid = (const struct reg_sequence[]) {
+ { 0x40, 0xfc987 },
+ },
+ .cfg_lmh_sid = (const struct reg_sequence[]) {
+ { 0x30, 0x77706db },
+ { 0x34, 0x5550249 },
+ { 0x38, 0x111 },
+ },
+ .cfg_ramp_pre_en = (const struct reg_sequence[]) {
+ { 0x50, 0x800 },
+ },
+ .cfg_ramp_en = (const struct reg_sequence[]) {
+ { 0x50, 0xc00 },
+ },
+ .cfg_ramp_post_en = (const struct reg_sequence[]) {
+ { 0x50, 0x400 },
+ },
+ .cfg_ramp_dis = (const struct reg_sequence[]) {
+ { 0x50, 0x0 },
+ },
+ .cmd_reg = 0x0,
+
+ .num_dfs_sids = 8,
+ .num_lmh_sids = 3,
+ .num_link_sids = 1,
+};
+
+static int qcom_ramp_controller_probe(struct platform_device *pdev)
+{
+ struct qcom_ramp_controller *qrc;
+ void __iomem *base;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ qrc = devm_kmalloc(&pdev->dev, sizeof(*qrc), GFP_KERNEL);
+ if (!qrc)
+ return -ENOMEM;
+
+ qrc->desc = device_get_match_data(&pdev->dev);
+ if (!qrc)
+ return -EINVAL;
+
+ qrc->regmap = devm_regmap_init_mmio(&pdev->dev, base, &qrc_regmap_config);
+ if (IS_ERR(qrc->regmap))
+ return PTR_ERR(qrc->regmap);
+
+ platform_set_drvdata(pdev, qrc);
+
+ return qcom_ramp_controller_start(qrc);
+}
+
+static int qcom_ramp_controller_remove(struct platform_device *pdev)
+{
+ struct qcom_ramp_controller *qrc = platform_get_drvdata(pdev);
+
+ return rc_write_cfg(qrc, qrc->desc->cfg_ramp_dis, RC_DCVS_CFG_SID, 1);
+}
+
+static const struct of_device_id qcom_ramp_controller_match_table[] = {
+ { .compatible = "qcom,msm8976-ramp-controller", .data = &msm8976_rc_cfg },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, qcom_ramp_controller_match_table);
+
+static struct platform_driver qcom_ramp_controller_driver = {
+ .driver = {
+ .name = "qcom-ramp-controller",
+ .of_match_table = qcom_ramp_controller_match_table,
+ .suppress_bind_attrs = true,
+ },
+ .probe = qcom_ramp_controller_probe,
+ .remove = qcom_ramp_controller_remove,
+};
+
+static int __init qcom_ramp_controller_init(void)
+{
+ return platform_driver_register(&qcom_ramp_controller_driver);
+}
+arch_initcall(qcom_ramp_controller_init);
+
+MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>");
+MODULE_DESCRIPTION("Qualcomm Ramp Controller driver");
+MODULE_LICENSE("GPL");