Re: [PATCH v3 3/8] qcom: spm: Add Subsystem Power Manager driver (SAW2)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Aug 18, 2014, at 5:23 PM, Lina Iyer <lina.iyer@xxxxxxxxxx> 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.
> 
> Allow drivers to configure the idle mode for the cores in the SPM start
> address register.

What is the ‘start address’?

Can we add anything about ‘start address’ and the sequences in the commit message?

> 
> Signed-off-by: Praveen Chidambaram <pchidamb@xxxxxxxxxxxxxx>
> Signed-off-by: Lina Iyer <lina.iyer@xxxxxxxxxx>
> ---
> drivers/soc/qcom/Makefile     |   1 +
> drivers/soc/qcom/spm.c        | 176 ++++++++++++++++++++++++++++++++++++++++++
> drivers/soc/qcom/spm_driver.h |  85 ++++++++++++++++++++
> 3 files changed, 262 insertions(+)
> create mode 100644 drivers/soc/qcom/spm.c
> create mode 100644 drivers/soc/qcom/spm_driver.h
> 
> 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

We should have the Kconfig that adds QCOM_PM as part of this patch so it actual is buildable.

> diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
> new file mode 100644
> index 0000000..3e1de43
> --- /dev/null
> +++ b/drivers/soc/qcom/spm.c
> @@ -0,0 +1,176 @@
> +/* 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_driver.h"
> +
> +#define NUM_SPM_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,
> +};

Why do we need an array for these offsets, can’t they just be #defines?

> +
> +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev,
> +		unsigned int reg_index)
> +{
> +	__raw_writel(dev->reg_shadow[reg_index],
> +			dev->reg_base_addr + dev->reg_offsets[reg_index]);
> +}
> +
> +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev,
> +		unsigned int reg_index)
> +{
> +	dev->reg_shadow[reg_index] = __raw_readl(dev->reg_base_addr +
> +						dev->reg_offsets[reg_index]);
> +}
> +
> +static inline void msm_spm_drv_set_start_addr(
> +		struct msm_spm_driver_data *dev, uint32_t addr)
> +{
> +	addr &= 0x7F;
> +	addr <<= 4;

possibly worth a comment why we are shifting addr

> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F;
> +	dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr;
> +}
> +
> +inline int msm_spm_drv_set_spm_enable(
> +		struct msm_spm_driver_data *dev, bool enable)
> +{
> +	uint32_t value = enable ? 0x01 : 0x00;
> +
> +	if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) {
> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1;
> +		dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value;
> +		msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
> +		wmb();

typically barriers should have comments about why they are needed.

> +	}
> +
> +	return 0;
> +}
> +
> +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev)

Can this be static?

> +{
> +	int i;
> +
> +	for (i = 0; i < NUM_SPM_ENTRY; i++) {
> +		__raw_writel(dev->reg_seq_entry_shadow[i],
> +			dev->reg_base_addr
> +			+ dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY]
> +			+ 4 * i);
> +	}
> +	mb();

again, comment about barrier usage.

> +}
> +
> +/**
> + * msm_spm_drv_write_seq_data - Load the SPM register with sequences
> + *
> + * @dev - The SPM device whose sequences to be programmed
> + * @cmd - The byte array
> + * @offset - The last written byte position, to continue from.
> + */
> +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
> +		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 = dev->reg_seq_entry_shadow[offset_w];
> +
> +		for (i = (*offset % 4); i < 4; i++) {
> +			last_cmd = *(cmd++);
> +			cmd_w |=  last_cmd << (i * 8);
> +			(*offset)++;
> +			if (last_cmd == 0x0f)
> +				break;
> +		}
> +
> +		dev->reg_seq_entry_shadow[offset_w++] = cmd_w;
> +		if (last_cmd == 0x0f)
> +			break;
> +	}
> +
> +	return 0;
> +}
> +
> +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
> +		uint32_t addr)
> +{
> +
> +	msm_spm_drv_set_start_addr(dev, addr);
> +	msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
> +	wmb();

comment on barrier

> +	msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
> +
> +	return 0;
> +}
> +

We should add a comment about what reinit is for.

> +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev)
> +{
> +	int i;
> +
> +	msm_spm_drv_flush_seq_entry(dev);
> +
> +	for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++)
> +		msm_spm_drv_load_shadow(dev, i);
> +}
> +

can we have the caller either use msm_spm_driver_data or just pass in reg_base_addr & reg_init_values as args to the function?
> 
> +int msm_spm_drv_init(struct msm_spm_driver_data *dev,
> +		struct msm_spm_platform_data *data)
> +{
> +	dev->reg_base_addr = data->reg_base_addr;
> +	memcpy(dev->reg_shadow, data->reg_init_values,
> +			sizeof(data->reg_init_values));
> +	dev->reg_offsets = msm_spm_reg_offsets_saw2_v2_1;
> +	dev->reg_seq_entry_shadow = kcalloc(NUM_SPM_ENTRY,
> +					sizeof(*dev->reg_seq_entry_shadow),
> +					GFP_KERNEL);
> +	if (!dev->reg_seq_entry_shadow)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h
> new file mode 100644
> index 0000000..6ff69bb
> --- /dev/null
> +++ b/drivers/soc/qcom/spm_driver.h
> @@ -0,0 +1,85 @@
> +/* 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_seq_entry {
> +	uint32_t mode;
> +	uint8_t *cmd;
> +};
> +
> +struct msm_spm_platform_data {
> +	void __iomem *reg_base_addr;
> +	uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE];
> +
> +	uint32_t num_modes;
> +	struct msm_spm_seq_entry *modes;

Are modes used?  is msm_spm_seq_entry used anywhere?

> +};
> +
> +struct msm_spm_driver_data {
> +	void __iomem *reg_base_addr;
> +	uint32_t reg_shadow[MSM_SPM_REG_NR];
> +	uint32_t *reg_seq_entry_shadow;
> +	uint32_t *reg_offsets;
> +	uint32_t num_spm_entry;
> +};

Is there a reason we need both platform_dta & driver_data ?

> +
> +int msm_spm_drv_init(struct msm_spm_driver_data *dev,
> +		struct msm_spm_platform_data *data);
> +void msm_spm_drv_reinit(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_write_seq_data(struct msm_spm_driver_data *dev,
> +		uint8_t *cmd, uint32_t *offset);
> +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev);

does flush need to be exposed?

> +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev,
> +		bool enable);
> +void msm_spm_reinit(void);
> +int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs);
> +
> +#endif /* __QCOM_SPM_DRIVER_H */
> -- 
> 1.9.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

-- 
Employee of Qualcomm Innovation Center, Inc.
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@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux