Message ID | 1408486537-6358-4-git-send-email-lina.iyer@linaro.org |
---|---|
State | New |
Headers | show |
On 08/19/14 15:15, Lina Iyer wrote: > SPM is a hardware block that controls the peripheral logic surrounding > the application cores (cpu/l$). When the core executes WFI instruction, > the SPM takes over the putting the core in low power state as > configured. The wake up for the SPM is an interrupt at the GIC, which > then completes the rest of low power mode sequence and brings the core > out of low power mode. > > The SPM has a set of control registers that configure the SPMs > individually based on the type of the core and the runtime conditions. > SPM is a finite state machine block to which a sequence is provided and > it interprets the bytes and executes them in sequence. Each low power > mode that the core can enter into is provided to the SPM as a sequence. > > Configure the SPM to set the core (cpu or L2) into its low power mode, > the index of the first command in the sequence is set in the SPM_CTL > register. When the core executes ARM wfi instruction, it triggers the > SPM state machine to start executing from that index. The SPM state > machine waits until the interrupt occurs and starts executing the rest > of the sequence until it hits the end of the sequence. The end of the > sequence jumps the core out of its low power mode. > > Signed-off-by: Praveen Chidambaram <pchidamb@codeaurora.org> > Signed-off-by: Lina Iyer <lina.iyer@linaro.org> Why isn't this code combined with the cpuidle driver in qcom-cpuidle.c, spm-devices.c, and qcom-pm.c? All these files are interacting with the same hardware, I'm confused why we have to have 4 different files and all these different patches to support this. Basically patches 3, 4, 6 and 7 should be one file under drivers/cpuidle/ named qcom-cpuidle or saw-cpuidle. > > diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c > new file mode 100644 > index 0000000..19b79d0 > --- /dev/null > +++ b/drivers/soc/qcom/spm.c > @@ -0,0 +1,195 @@ [..] > + > +static void flush_shadow(struct msm_spm_driver_data *drv, > + unsigned int reg_index) > +{ > + __raw_writel(drv->reg_shadow[reg_index], > + drv->reg_base_addr + drv->reg_offsets[reg_index]); > +} What's the use of the "shadow" functionality? Why can't we just read and write the registers directly without having to go through a register cache? > + > +static void load_shadow(struct msm_spm_driver_data *drv, > + unsigned int reg_index) > +{ > + drv->reg_shadow[reg_index] = __raw_readl(drv->reg_base_addr + > + drv->reg_offsets[reg_index]); Please use readl_relaxed/writel_relaxed. The raw accessors don't byte-swap and fail horribly for big-endian kernels. > +} > + > +static inline void set_start_addr(struct msm_spm_driver_data *drv, > + uint32_t addr) > +{ > + /* Update bits 10:4 in the SPM CTL register */ > + addr &= 0x7F; > + addr <<= 4; > + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F; > + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr; > +} > + > +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *drv, > + uint32_t mode) > +{ > + int i; > + uint32_t start_addr = 0; > + > + for (i = 0; i < drv->num_modes; i++) { > + if (drv->modes[i].mode == mode) { > + start_addr = drv->modes[i].start_addr; > + break; > + } > + } > + > + if (i == drv->num_modes) > + return -EINVAL; > + > + set_start_addr(drv, start_addr); > + flush_shadow(drv, MSM_SPM_REG_SAW2_SPM_CTL); > + /* Barrier to ensure we have written the start address */ > + wmb(); > + > + /* Update our shadow with the status changes, if any */ > + load_shadow(drv, MSM_SPM_REG_SAW2_SPM_STS); > + > + return 0; > +} > + > +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *drv, bool enable) > +{ > + uint32_t value = enable ? 0x01 : 0x00; I would prefer u32/u8 instead of uint32_t. Makes lines shorter and easier to read. > + > + /* Update BIT(0) of SPM_CTL to enable/disable the SPM */ This comment could be replaced with code that's more english-like. #define SPM_CTL_ENABLE BIT(0) if ((drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & SPM_CTL_ENABLE) != value) > + if ((drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) { > + /* Clear the existing value and update */ > + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1; > + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value; > + flush_shadow(drv, MSM_SPM_REG_SAW2_SPM_CTL); > + /* Ensure we have enabled/disabled before returning */ > + wmb(); > + } > + > + return 0; Why have a return value at all?
On Tue, Aug 19, 2014 at 07:01:53PM -0700, Stephen Boyd wrote: >On 08/19/14 15:15, Lina Iyer wrote: >> SPM is a hardware block that controls the peripheral logic surrounding >> the application cores (cpu/l$). When the core executes WFI instruction, >> the SPM takes over the putting the core in low power state as >> configured. The wake up for the SPM is an interrupt at the GIC, which >> then completes the rest of low power mode sequence and brings the core >> out of low power mode. >> >> The SPM has a set of control registers that configure the SPMs >> individually based on the type of the core and the runtime conditions. >> SPM is a finite state machine block to which a sequence is provided and >> it interprets the bytes and executes them in sequence. Each low power >> mode that the core can enter into is provided to the SPM as a sequence. >> >> Configure the SPM to set the core (cpu or L2) into its low power mode, >> the index of the first command in the sequence is set in the SPM_CTL >> register. When the core executes ARM wfi instruction, it triggers the >> SPM state machine to start executing from that index. The SPM state >> machine waits until the interrupt occurs and starts executing the rest >> of the sequence until it hits the end of the sequence. The end of the >> sequence jumps the core out of its low power mode. >> >> Signed-off-by: Praveen Chidambaram <pchidamb@codeaurora.org> >> Signed-off-by: Lina Iyer <lina.iyer@linaro.org> > >Why isn't this code combined with the cpuidle driver in qcom-cpuidle.c, >spm-devices.c, and qcom-pm.c? All these files are interacting with the >same hardware, I'm confused why we have to have 4 different files and >all these different patches to support this. Basically patches 3, 4, 6 >and 7 should be one file under drivers/cpuidle/ named qcom-cpuidle or >saw-cpuidle. They all interact with the one hardware, you are right about that. But each of these have a responsibility and provide certain functionality that builds up into the idle framework. Let me explain - The hardware driver spm.c doesnt care what the code calling the driver, intends to do. All it knows is to write to right register. And it can write to only one SPM block. There are multiple SPM blocks which need to be managed and thats provided by spm-devices. The cpuidle framework or the hotplug framework needs to do the same thing. The common functionality between idle and hotplug is abstraced out in msm-pm.c. platsmp.c and cpuidle-qcom.c both would need the same functionality. The SPM is not simple register write. It needs quite a bit of configuration and to make it functional and then you may do register writes. SPM also provides voltage control functionality, which again has a lot of support code that need to ensure the hardware is in the correct state before you do that one register write to set the voltage. Again, this and other functionality add up to a whole lot of code to be clumped up in qcom-cpuidle.c and duplicated for hotplug again. It is beneficial to have them this way. Bear with me, as I build up this framework to support the idle and hotplug idle framework. > >> >> diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c >> new file mode 100644 >> index 0000000..19b79d0 >> --- /dev/null >> +++ b/drivers/soc/qcom/spm.c >> @@ -0,0 +1,195 @@ >[..] >> + >> +static void flush_shadow(struct msm_spm_driver_data *drv, >> + unsigned int reg_index) >> +{ >> + __raw_writel(drv->reg_shadow[reg_index], >> + drv->reg_base_addr + drv->reg_offsets[reg_index]); >> +} > >What's the use of the "shadow" functionality? Why can't we just read and >write the registers directly without having to go through a register cache? Helps speed up reads and write, when you have multiple writes. Also, is an excellent mechanism to know the state SPM was configured to, in the event of a mishap. > >> + >> +static void load_shadow(struct msm_spm_driver_data *drv, >> + unsigned int reg_index) >> +{ >> + drv->reg_shadow[reg_index] = __raw_readl(drv->reg_base_addr + >> + drv->reg_offsets[reg_index]); > >Please use readl_relaxed/writel_relaxed. The raw accessors don't >byte-swap and fail horribly for big-endian kernels. OK. But does it matter that the SoC the code is expected to run is little-endian? > >> +} >> + >> +static inline void set_start_addr(struct msm_spm_driver_data *drv, >> + uint32_t addr) >> +{ >> + /* Update bits 10:4 in the SPM CTL register */ >> + addr &= 0x7F; >> + addr <<= 4; >> + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F; >> + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr; >> +} >> + >> +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *drv, >> + uint32_t mode) >> +{ >> + int i; >> + uint32_t start_addr = 0; >> + >> + for (i = 0; i < drv->num_modes; i++) { >> + if (drv->modes[i].mode == mode) { >> + start_addr = drv->modes[i].start_addr; >> + break; >> + } >> + } >> + >> + if (i == drv->num_modes) >> + return -EINVAL; >> + >> + set_start_addr(drv, start_addr); >> + flush_shadow(drv, MSM_SPM_REG_SAW2_SPM_CTL); >> + /* Barrier to ensure we have written the start address */ >> + wmb(); >> + >> + /* Update our shadow with the status changes, if any */ >> + load_shadow(drv, MSM_SPM_REG_SAW2_SPM_STS); >> + >> + return 0; >> +} >> + >> +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *drv, bool enable) >> +{ >> + uint32_t value = enable ? 0x01 : 0x00; > >I would prefer u32/u8 instead of uint32_t. Makes lines shorter and >easier to read. > Hmm.. okay >> + >> + /* Update BIT(0) of SPM_CTL to enable/disable the SPM */ > >This comment could be replaced with code that's more english-like. Ok. > > #define SPM_CTL_ENABLE BIT(0) > > if ((drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & SPM_CTL_ENABLE) != >value) > >> + if ((drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) { >> + /* Clear the existing value and update */ >> + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1; >> + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value; >> + flush_shadow(drv, MSM_SPM_REG_SAW2_SPM_CTL); >> + /* Ensure we have enabled/disabled before returning */ >> + wmb(); >> + } >> + >> + return 0; > >Why have a return value at all? > Well, it was intended to be an export function. Made sense that way that the caller may expect an error. if the hardware was not intialized. I removed that code here, but did not take out the return value. >-- >Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, >hosted by The Linux Foundation > -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Wed, Aug 20, 2014 at 05:25:49PM -0700, Stephen Boyd wrote: >On 08/19/14 20:24, Lina Iyer wrote: >> On Tue, Aug 19, 2014 at 07:01:53PM -0700, Stephen Boyd wrote: >>> On 08/19/14 15:15, Lina Iyer wrote: >>>> SPM is a hardware block that controls the peripheral logic surrounding >>>> the application cores (cpu/l$). When the core executes WFI instruction, >>>> the SPM takes over the putting the core in low power state as >>>> configured. The wake up for the SPM is an interrupt at the GIC, which >>>> then completes the rest of low power mode sequence and brings the core >>>> out of low power mode. >>>> >>>> The SPM has a set of control registers that configure the SPMs >>>> individually based on the type of the core and the runtime conditions. >>>> SPM is a finite state machine block to which a sequence is provided and >>>> it interprets the bytes and executes them in sequence. Each low power >>>> mode that the core can enter into is provided to the SPM as a sequence. >>>> >>>> Configure the SPM to set the core (cpu or L2) into its low power mode, >>>> the index of the first command in the sequence is set in the SPM_CTL >>>> register. When the core executes ARM wfi instruction, it triggers the >>>> SPM state machine to start executing from that index. The SPM state >>>> machine waits until the interrupt occurs and starts executing the rest >>>> of the sequence until it hits the end of the sequence. The end of the >>>> sequence jumps the core out of its low power mode. >>>> >>>> Signed-off-by: Praveen Chidambaram <pchidamb@codeaurora.org> >>>> Signed-off-by: Lina Iyer <lina.iyer@linaro.org> >>> >>> Why isn't this code combined with the cpuidle driver in qcom-cpuidle.c, >>> spm-devices.c, and qcom-pm.c? All these files are interacting with the >>> same hardware, I'm confused why we have to have 4 different files and >>> all these different patches to support this. Basically patches 3, 4, 6 >>> and 7 should be one file under drivers/cpuidle/ named qcom-cpuidle or >>> saw-cpuidle. >> >> They all interact with the one hardware, you are right about that. But >> each >> of these have a responsibility and provide certain functionality that >> builds up into the idle framework. >> Let me explain - The hardware driver spm.c doesnt care what the code >> calling the driver, intends to do. All it knows is to write to right >> register. And it can write to only one SPM block. There are multiple SPM >> blocks which need to be managed and thats provided by spm-devices. The >> cpuidle framework or the hotplug framework needs to do the same thing. >> The common functionality between idle and hotplug is abstraced out in >> msm-pm.c. platsmp.c and cpuidle-qcom.c both would need the same >> functionality. > >I can see the end result in the downstream vendor tree and it isn't >pretty. The code winds through a handful of different files. The spm.c >file is basically just a bunch of functions to read and write to an SPM. >By itself it's pretty much worthless and doesn't stand on its own. I wouldnt put it that way. It abstracts a whole of device functionality away from rest of the code. If look at the history of the driver in the downstream, you would undertand that multiple version of SPM exists and at some point, they all were supported by the same code base. The spm.c is valuable that way in abstracting the bit manipulations away from managing the device. Different version manipulate the same registers differently and I can guarantee that this wont be the last of the h/w revision either. A separate file helps keep the implementations clean and manage different versions of SPM better. >spm-devices is where we would actually probe a driver for the device >that is read/written in spm.c. At the least, these two files should be >combined into one driver. > Thats very simplistic approach. Reiterating, what I mention earlier, the driver when clubbed, would get complicated and real huge, very soon. >Right now just to support cpuidle it seems that it could all be in one >file. When we get to hotplug, why don't we use cpuidle_play_dead() on >ARM so that this cpuidle driver can configure the SPM for the deepest >idle state and power off the CPU? The only problem I see is that we >would need another hook for the cpu_kill() op so that we can wait for >the state machine to finish bringing down the CPU. That would remove the >need for msm-pm.c? I will explore the cpuidle_play_dead() to see how much we can reuse there. This file is at its very infancy, only switches the low power mode to the right function, but wait until you have to handle two different kinds of processors - Krait and vanilla ARM cores using the same code base for the same vendor. And the many corner cases you need to solve. What we do if we remove msm-pm.c at this point is build up all the code in cpuidle and when it comes to differenciating and handle corner cases, we will split the file again to abstract things better. If you had followed the previous versions of this patch, you would realize, that I had pruned a whole lot of code. Removing this file would make putting those code back really difficult and the driver needs all that code. Downstream is dictated by the milliamp of power saving you can squeeze out of the SoC and it is inevitable that we would have to handle atleast some of these usecases. Broad spectrum generic low power mode, barely gets the debug boards running. If our goal is to have upstream code any close to being as functional as downstream, you would need to do a multitude of things and clubbing them all in one procedural file does not help the effort. The cover letter explains how the hierarchy is and the patches will evolve the hierarchy to full functionality. >This would handle the cpuidle_play_dead() part. Alternatively we call >cpuidle_play_dead() from the cpu_die() hook in the platform layer, but >I'd prefer we call it directly in arch code if we can. > >diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c >index 7c4fada440f0..fef953ecde22 100644 >--- a/arch/arm/kernel/smp.c >+++ b/arch/arm/kernel/smp.c >@@ -26,6 +26,7 @@ > #include <linux/completion.h> > #include <linux/cpufreq.h> > #include <linux/irq_work.h> >+#include <linux/cpuidle.h> > > #include <linux/atomic.h> > #include <asm/smp.h> >@@ -290,8 +291,9 @@ void __ref cpu_die(void) > * The return path should not be used for platforms which can > * power off the CPU. > */ >- if (smp_ops.cpu_die) >- smp_ops.cpu_die(cpu); >+ if (cpuidle_play_dead()) >+ if (smp_ops.cpu_die) >+ smp_ops.cpu_die(cpu); > > pr_warn("CPU%u: smp_ops.cpu_die() returned, trying to resuscitate\n", > cpu); > > >> >> The SPM is not simple register write. It needs quite a bit of >> configuration and to make it functional and then you may do register >> writes. SPM also provides voltage control functionality, which again >> has a lot of support code that need to ensure the hardware is in the >> correct state before you do that one register write to set the voltage. >> Again, this and other functionality add up to a whole lot of code to be >> clumped up in qcom-cpuidle.c and duplicated for hotplug again. It is >> beneficial to have them this way. Bear with me, as I build up this >> framework to support the idle and hotplug idle framework. >> > >Yes I'm not quite sure how we would handle adding the regulator code. In >some cases each CPU's SAW is controlling a regulator and in other cases >there's only the L2 SAW controlling a regulator. We would need these >regulators to exist even if we don't support cpuidle, so we'd need to >register it somehow. I was wondering if we shouldn't be using the >register in the SAW to change the voltage, instead we should go directly >to the PMIC and write the regulator driver on top of the PMIC interface. >The spm code could then look at those regulators to figure out what >voltage the supply is configured to so that it can be programmed into >the SPM and used during any power restoring activities. I'm not sure if >we can even write the PMIC registers though, or if those registers are >locked down thus requiring us to use the SPM interface to change >voltages. It would be nice to split this out somehow though. Another >idea would be to make some shim driver that creates two child devices >for the regulator and cpuidle drivers and have the shim driver make a >regmap that both drivers use. > There is not just one SPM in the system. If you can split between the regulator and idle functionality of the same hardware, imaging having 10s of such SPMs that you need to manage. Like you mention, the functionality of an instance of SPM varies depending on the SoC architecture. And to keep a track of which SPM changes the voltage is not functionality that you want the regulator code to remember. Today you might get by, bit banging or I2C or SPMI write to the PMIC, but it inevitable in the future and it would unacceptably inefficient to do so. >>>> >>>> diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c >>>> new file mode 100644 >>>> index 0000000..19b79d0 >>>> --- /dev/null >>>> +++ b/drivers/soc/qcom/spm.c >>>> @@ -0,0 +1,195 @@ >>> [..] >>>> + >>>> +static void flush_shadow(struct msm_spm_driver_data *drv, >>>> + unsigned int reg_index) >>>> +{ >>>> + __raw_writel(drv->reg_shadow[reg_index], >>>> + drv->reg_base_addr + drv->reg_offsets[reg_index]); >>>> +} >>> >>> What's the use of the "shadow" functionality? Why can't we just read and >>> write the registers directly without having to go through a register >>> cache? >> Helps speed up reads and write, when you have multiple writes. Also, is >> an excellent mechanism to know the state SPM was configured to, in the >> event of a mishap. > >Huh? How can writing something into memory and then writing it to the >device be faster than writing it directly to the device? If we need to >know how the SPM was configured I assume we would know by printks or by >inspecting the code. I still don't see a reason for this. > It is if you dont flush after every write. There are explicit flushes to help flush the whole register set, whenever many elements in the set are modified. printks/traces are not very helpful when the log buffer is not completely written in the power down/up path. When you dont have a running kernel, looking at system state and predicting what could have happened to crash the system or not jump back into warmboot entry point, is very tough. This is even more bare than the bare minimum of code that you need to debug and maintain a PM driver on a production device. >>>> + >>>> +static void load_shadow(struct msm_spm_driver_data *drv, >>>> + unsigned int reg_index) >>>> +{ >>>> + drv->reg_shadow[reg_index] = __raw_readl(drv->reg_base_addr + >>>> + drv->reg_offsets[reg_index]); >>> >>> Please use readl_relaxed/writel_relaxed. The raw accessors don't >>> byte-swap and fail horribly for big-endian kernels. >> OK. But does it matter that the SoC the code is expected to run is >> little-endian? > >big-endian kernels work on these platforms. Nobody is extensively >testing it but putting down more roadblocks to using it isn't helpful. > Fair enough. I will change the functions. Thanks for your time. Lina >-- >Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, >hosted by The Linux Foundation > -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 70d52ed..20b329f 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o +obj-$(CONFIG_QCOM_PM) += spm.o CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o diff --git a/drivers/soc/qcom/spm-drv.h b/drivers/soc/qcom/spm-drv.h new file mode 100644 index 0000000..4f41f1b --- /dev/null +++ b/drivers/soc/qcom/spm-drv.h @@ -0,0 +1,71 @@ +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __QCOM_SPM_DRIVER_H +#define __QCOM_SPM_DRIVER_H + +enum { + MSM_SPM_REG_SAW2_CFG, + MSM_SPM_REG_SAW2_AVS_CTL, + MSM_SPM_REG_SAW2_AVS_HYSTERESIS, + MSM_SPM_REG_SAW2_SPM_CTL, + MSM_SPM_REG_SAW2_PMIC_DLY, + MSM_SPM_REG_SAW2_AVS_LIMIT, + MSM_SPM_REG_SAW2_AVS_DLY, + MSM_SPM_REG_SAW2_SPM_DLY, + MSM_SPM_REG_SAW2_PMIC_DATA_0, + MSM_SPM_REG_SAW2_PMIC_DATA_1, + MSM_SPM_REG_SAW2_PMIC_DATA_2, + MSM_SPM_REG_SAW2_PMIC_DATA_3, + MSM_SPM_REG_SAW2_PMIC_DATA_4, + MSM_SPM_REG_SAW2_PMIC_DATA_5, + MSM_SPM_REG_SAW2_PMIC_DATA_6, + MSM_SPM_REG_SAW2_PMIC_DATA_7, + MSM_SPM_REG_SAW2_RST, + + MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST, + + MSM_SPM_REG_SAW2_ID, + MSM_SPM_REG_SAW2_SECURE, + MSM_SPM_REG_SAW2_STS0, + MSM_SPM_REG_SAW2_STS1, + MSM_SPM_REG_SAW2_STS2, + MSM_SPM_REG_SAW2_VCTL, + MSM_SPM_REG_SAW2_SEQ_ENTRY, + MSM_SPM_REG_SAW2_SPM_STS, + MSM_SPM_REG_SAW2_AVS_STS, + MSM_SPM_REG_SAW2_PMIC_STS, + MSM_SPM_REG_SAW2_VERSION, + + MSM_SPM_REG_NR, +}; + +struct msm_spm_mode { + uint32_t mode; + uint8_t *cmd; + uint32_t start_addr; +}; + +struct msm_spm_driver_data { + void __iomem *reg_base_addr; + uint32_t reg_shadow[MSM_SPM_REG_NR]; + uint32_t *reg_offsets; + struct msm_spm_mode *modes; + uint32_t num_modes; +}; + +int msm_spm_drv_init(struct msm_spm_driver_data *dev); +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, + uint32_t addr); +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev, + bool enable); + +#endif /* __QCOM_SPM_DRIVER_H */ diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c new file mode 100644 index 0000000..19b79d0 --- /dev/null +++ b/drivers/soc/qcom/spm.c @@ -0,0 +1,195 @@ +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/slab.h> + +#include "spm-drv.h" + +#define NUM_SEQ_ENTRY 32 + +static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = { + [MSM_SPM_REG_SAW2_SECURE] = 0x00, + [MSM_SPM_REG_SAW2_ID] = 0x04, + [MSM_SPM_REG_SAW2_CFG] = 0x08, + [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C, + [MSM_SPM_REG_SAW2_AVS_STS] = 0x10, + [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14, + [MSM_SPM_REG_SAW2_RST] = 0x18, + [MSM_SPM_REG_SAW2_VCTL] = 0x1C, + [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20, + [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24, + [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28, + [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C, + [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30, + [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34, + [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40, + [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44, + [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48, + [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C, + [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50, + [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, + [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, + [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, + [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x80, + [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, +}; + +static void flush_shadow(struct msm_spm_driver_data *drv, + unsigned int reg_index) +{ + __raw_writel(drv->reg_shadow[reg_index], + drv->reg_base_addr + drv->reg_offsets[reg_index]); +} + +static void load_shadow(struct msm_spm_driver_data *drv, + unsigned int reg_index) +{ + drv->reg_shadow[reg_index] = __raw_readl(drv->reg_base_addr + + drv->reg_offsets[reg_index]); +} + +static inline void set_start_addr(struct msm_spm_driver_data *drv, + uint32_t addr) +{ + /* Update bits 10:4 in the SPM CTL register */ + addr &= 0x7F; + addr <<= 4; + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F; + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr; +} + +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *drv, + uint32_t mode) +{ + int i; + uint32_t start_addr = 0; + + for (i = 0; i < drv->num_modes; i++) { + if (drv->modes[i].mode == mode) { + start_addr = drv->modes[i].start_addr; + break; + } + } + + if (i == drv->num_modes) + return -EINVAL; + + set_start_addr(drv, start_addr); + flush_shadow(drv, MSM_SPM_REG_SAW2_SPM_CTL); + /* Barrier to ensure we have written the start address */ + wmb(); + + /* Update our shadow with the status changes, if any */ + load_shadow(drv, MSM_SPM_REG_SAW2_SPM_STS); + + return 0; +} + +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *drv, bool enable) +{ + uint32_t value = enable ? 0x01 : 0x00; + + /* Update BIT(0) of SPM_CTL to enable/disable the SPM */ + if ((drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) { + /* Clear the existing value and update */ + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1; + drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value; + flush_shadow(drv, MSM_SPM_REG_SAW2_SPM_CTL); + /* Ensure we have enabled/disabled before returning */ + wmb(); + } + + return 0; +} + +static void flush_seq_data(struct msm_spm_driver_data *drv, + uint32_t *reg_seq_entry) +{ + int i; + + /* Write the 32 byte array into the SPM registers */ + for (i = 0; i < NUM_SEQ_ENTRY; i++) { + __raw_writel(reg_seq_entry[i], + drv->reg_base_addr + + drv->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY] + + 4 * i); + } + /* Ensure that the changes are written */ + wmb(); +} + +static void write_seq_data(struct msm_spm_driver_data *drv, + uint32_t *reg_seq_entry, uint8_t *cmd, uint32_t *offset) +{ + uint32_t cmd_w; + uint32_t offset_w = *offset / 4; + uint8_t last_cmd; + + while (1) { + int i; + + cmd_w = 0; + last_cmd = 0; + cmd_w = reg_seq_entry[offset_w]; + + for (i = (*offset % 4); i < 4; i++) { + last_cmd = *(cmd++); + cmd_w |= last_cmd << (i * 8); + (*offset)++; + if (last_cmd == 0x0f) + break; + } + + reg_seq_entry[offset_w++] = cmd_w; + if (last_cmd == 0x0f) + break; + } + +} + +int msm_spm_drv_init(struct msm_spm_driver_data *drv) +{ + int i; + int offset = 0; + uint32_t sequences[NUM_SEQ_ENTRY/sizeof(uint8_t)] = {0}; + + drv->reg_offsets = msm_spm_reg_offsets_saw2_v2_1; + + /** + * Compose the uint32 array based on the individual bytes of the SPM + * sequence for each low power mode that we read from the DT. + * The sequences are appended if there is space available in the + * integer after the end of the previous sequence. + */ + for (i = 0; i < drv->num_modes; i++) { + drv->modes[i].start_addr = offset; + write_seq_data(drv, &sequences[0], drv->modes[i].cmd, &offset); + } + + /* Flush the integer array */ + flush_seq_data(drv, &sequences[0]); + + /** + * Initialize the hardware with the control registers that + * we have read. + */ + for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0; i++) + flush_shadow(drv, i); + + return 0; +}