mirror of https://github.com/torvalds/linux.git
pmdomain: marvell: Add PXA1908 power domains
Marvell's PXA1908 SoC has a few power domains for its VPU, GPU, image processor and DSI PHY. Add a driver to control these. Signed-off-by: Duje Mihanović <duje@dujemihanovic.xyz> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
parent
614106a731
commit
6f51a04551
|
|
@ -2871,6 +2871,7 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
|
|||
S: Maintained
|
||||
F: arch/arm64/boot/dts/marvell/mmp/
|
||||
F: drivers/clk/mmp/clk-pxa1908*.c
|
||||
F: drivers/pmdomain/marvell/
|
||||
F: include/dt-bindings/clock/marvell,pxa1908.h
|
||||
F: include/dt-bindings/power/marvell,pxa1908-power.h
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ source "drivers/pmdomain/apple/Kconfig"
|
|||
source "drivers/pmdomain/arm/Kconfig"
|
||||
source "drivers/pmdomain/bcm/Kconfig"
|
||||
source "drivers/pmdomain/imx/Kconfig"
|
||||
source "drivers/pmdomain/marvell/Kconfig"
|
||||
source "drivers/pmdomain/mediatek/Kconfig"
|
||||
source "drivers/pmdomain/qcom/Kconfig"
|
||||
source "drivers/pmdomain/renesas/Kconfig"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ obj-y += apple/
|
|||
obj-y += arm/
|
||||
obj-y += bcm/
|
||||
obj-y += imx/
|
||||
obj-y += marvell/
|
||||
obj-y += mediatek/
|
||||
obj-y += qcom/
|
||||
obj-y += renesas/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
menu "Marvell PM Domains"
|
||||
depends on ARCH_MMP || COMPILE_TEST
|
||||
|
||||
config PXA1908_PM_DOMAINS
|
||||
tristate "Marvell PXA1908 power domains"
|
||||
depends on OF
|
||||
depends on PM
|
||||
default y if ARCH_MMP && ARM64
|
||||
select AUXILIARY_BUS
|
||||
select MFD_SYSCON
|
||||
select PM_GENERIC_DOMAINS
|
||||
select PM_GENERIC_DOMAINS_OF
|
||||
help
|
||||
Say Y here to enable support for Marvell PXA1908's power domanis.
|
||||
|
||||
endmenu
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
obj-$(CONFIG_PXA1908_PM_DOMAINS) += pxa1908-power-controller.o
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright 2025 Duje Mihanović <duje@dujemihanovic.xyz>
|
||||
*/
|
||||
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <dt-bindings/power/marvell,pxa1908-power.h>
|
||||
|
||||
/* VPU, GPU, ISP */
|
||||
#define APMU_PWR_CTRL_REG 0xd8
|
||||
#define APMU_PWR_BLK_TMR_REG 0xdc
|
||||
#define APMU_PWR_STATUS_REG 0xf0
|
||||
|
||||
/* DSI */
|
||||
#define APMU_DEBUG 0x88
|
||||
#define DSI_PHY_DVM_MASK BIT(31)
|
||||
|
||||
#define POWER_ON_LATENCY_US 300
|
||||
#define POWER_OFF_LATENCY_US 20
|
||||
#define POWER_POLL_TIMEOUT_US (25 * USEC_PER_MSEC)
|
||||
#define POWER_POLL_SLEEP_US 6
|
||||
|
||||
#define NR_DOMAINS 5
|
||||
|
||||
#define to_pxa1908_pd(_genpd) container_of(_genpd, struct pxa1908_pd, genpd)
|
||||
|
||||
struct pxa1908_pd_ctrl {
|
||||
struct generic_pm_domain *domains[NR_DOMAINS];
|
||||
struct genpd_onecell_data onecell_data;
|
||||
struct regmap *base;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
struct pxa1908_pd_data {
|
||||
u32 reg_clk_res_ctrl;
|
||||
u32 pwr_state;
|
||||
u32 hw_mode;
|
||||
bool keep_on;
|
||||
int id;
|
||||
};
|
||||
|
||||
struct pxa1908_pd {
|
||||
const struct pxa1908_pd_data data;
|
||||
struct pxa1908_pd_ctrl *ctrl;
|
||||
struct generic_pm_domain genpd;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
static inline bool pxa1908_pd_is_on(struct pxa1908_pd *pd)
|
||||
{
|
||||
struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
|
||||
|
||||
return pd->data.id != PXA1908_POWER_DOMAIN_DSI
|
||||
? regmap_test_bits(ctrl->base, APMU_PWR_STATUS_REG, pd->data.pwr_state)
|
||||
: regmap_test_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
|
||||
}
|
||||
|
||||
static int pxa1908_pd_power_on(struct generic_pm_domain *genpd)
|
||||
{
|
||||
struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
|
||||
const struct pxa1908_pd_data *data = &pd->data;
|
||||
struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
|
||||
unsigned int status;
|
||||
int ret = 0;
|
||||
|
||||
regmap_set_bits(ctrl->base, data->reg_clk_res_ctrl, data->hw_mode);
|
||||
if (data->id != PXA1908_POWER_DOMAIN_ISP)
|
||||
regmap_write(ctrl->base, APMU_PWR_BLK_TMR_REG, 0x20001fff);
|
||||
regmap_set_bits(ctrl->base, APMU_PWR_CTRL_REG, data->pwr_state);
|
||||
|
||||
ret = regmap_read_poll_timeout(ctrl->base, APMU_PWR_STATUS_REG, status,
|
||||
status & data->pwr_state, POWER_POLL_SLEEP_US,
|
||||
POWER_ON_LATENCY_US + POWER_POLL_TIMEOUT_US);
|
||||
if (ret == -ETIMEDOUT)
|
||||
dev_err(ctrl->dev, "timed out powering on domain '%s'\n", pd->genpd.name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pxa1908_pd_power_off(struct generic_pm_domain *genpd)
|
||||
{
|
||||
struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
|
||||
const struct pxa1908_pd_data *data = &pd->data;
|
||||
struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
|
||||
unsigned int status;
|
||||
int ret;
|
||||
|
||||
regmap_clear_bits(ctrl->base, APMU_PWR_CTRL_REG, data->pwr_state);
|
||||
|
||||
ret = regmap_read_poll_timeout(ctrl->base, APMU_PWR_STATUS_REG, status,
|
||||
!(status & data->pwr_state), POWER_POLL_SLEEP_US,
|
||||
POWER_OFF_LATENCY_US + POWER_POLL_TIMEOUT_US);
|
||||
if (ret == -ETIMEDOUT) {
|
||||
dev_err(ctrl->dev, "timed out powering off domain '%s'\n", pd->genpd.name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return regmap_clear_bits(ctrl->base, data->reg_clk_res_ctrl, data->hw_mode);
|
||||
}
|
||||
|
||||
static inline int pxa1908_dsi_power_on(struct generic_pm_domain *genpd)
|
||||
{
|
||||
struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
|
||||
struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
|
||||
|
||||
return regmap_set_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
|
||||
}
|
||||
|
||||
static inline int pxa1908_dsi_power_off(struct generic_pm_domain *genpd)
|
||||
{
|
||||
struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
|
||||
struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
|
||||
|
||||
return regmap_clear_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
|
||||
}
|
||||
|
||||
#define DOMAIN(_id, _name, ctrl, mode, state) \
|
||||
[_id] = { \
|
||||
.data = { \
|
||||
.reg_clk_res_ctrl = ctrl, \
|
||||
.hw_mode = BIT(mode), \
|
||||
.pwr_state = BIT(state), \
|
||||
.id = _id, \
|
||||
}, \
|
||||
.genpd = { \
|
||||
.name = _name, \
|
||||
.power_on = pxa1908_pd_power_on, \
|
||||
.power_off = pxa1908_pd_power_off, \
|
||||
}, \
|
||||
}
|
||||
|
||||
static struct pxa1908_pd domains[NR_DOMAINS] = {
|
||||
DOMAIN(PXA1908_POWER_DOMAIN_VPU, "vpu", 0xa4, 19, 2),
|
||||
DOMAIN(PXA1908_POWER_DOMAIN_GPU, "gpu", 0xcc, 11, 0),
|
||||
DOMAIN(PXA1908_POWER_DOMAIN_GPU2D, "gpu2d", 0xf4, 11, 6),
|
||||
DOMAIN(PXA1908_POWER_DOMAIN_ISP, "isp", 0x38, 15, 4),
|
||||
[PXA1908_POWER_DOMAIN_DSI] = {
|
||||
.genpd = {
|
||||
.name = "dsi",
|
||||
.power_on = pxa1908_dsi_power_on,
|
||||
.power_off = pxa1908_dsi_power_off,
|
||||
/*
|
||||
* TODO: There is no DSI driver written yet and until then we probably
|
||||
* don't want to power off the DSI PHY ever.
|
||||
*/
|
||||
.flags = GENPD_FLAG_ALWAYS_ON,
|
||||
},
|
||||
.data = {
|
||||
/* See above. */
|
||||
.keep_on = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static void pxa1908_pd_remove(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct pxa1908_pd *pd;
|
||||
int ret;
|
||||
|
||||
for (int i = NR_DOMAINS - 1; i >= 0; i--) {
|
||||
pd = &domains[i];
|
||||
|
||||
if (!pd->initialized)
|
||||
continue;
|
||||
|
||||
if (pxa1908_pd_is_on(pd) && !pd->data.keep_on)
|
||||
pxa1908_pd_power_off(&pd->genpd);
|
||||
|
||||
ret = pm_genpd_remove(&pd->genpd);
|
||||
if (ret)
|
||||
dev_err(&auxdev->dev, "failed to remove domain '%s': %d\n",
|
||||
pd->genpd.name, ret);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
pxa1908_pd_init(struct pxa1908_pd_ctrl *ctrl, int id, struct device *dev)
|
||||
{
|
||||
struct pxa1908_pd *pd = &domains[id];
|
||||
int ret;
|
||||
|
||||
ctrl->domains[id] = &pd->genpd;
|
||||
|
||||
pd->ctrl = ctrl;
|
||||
|
||||
/* Make sure the state of the hardware is synced with the domain table above. */
|
||||
if (pd->data.keep_on) {
|
||||
ret = pd->genpd.power_on(&pd->genpd);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to power on domain '%s'\n",
|
||||
pd->genpd.name);
|
||||
} else {
|
||||
if (pxa1908_pd_is_on(pd)) {
|
||||
dev_warn(dev,
|
||||
"domain '%s' is on despite being default off; powering off\n",
|
||||
pd->genpd.name);
|
||||
|
||||
ret = pd->genpd.power_off(&pd->genpd);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"failed to power off domain '%s'\n",
|
||||
pd->genpd.name);
|
||||
}
|
||||
}
|
||||
|
||||
ret = pm_genpd_init(&pd->genpd, NULL, !pd->data.keep_on);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "domain '%s' failed to initialize\n",
|
||||
pd->genpd.name);
|
||||
|
||||
pd->initialized = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pxa1908_pd_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *aux_id)
|
||||
{
|
||||
struct pxa1908_pd_ctrl *ctrl;
|
||||
struct device *dev = &auxdev->dev;
|
||||
int ret;
|
||||
|
||||
ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
|
||||
if (!ctrl)
|
||||
return -ENOMEM;
|
||||
|
||||
auxiliary_set_drvdata(auxdev, ctrl);
|
||||
|
||||
ctrl->base = syscon_node_to_regmap(dev->parent->of_node);
|
||||
if (IS_ERR(ctrl->base))
|
||||
return dev_err_probe(dev, PTR_ERR(ctrl->base), "no regmap available\n");
|
||||
|
||||
ctrl->dev = dev;
|
||||
ctrl->onecell_data.domains = ctrl->domains;
|
||||
ctrl->onecell_data.num_domains = NR_DOMAINS;
|
||||
|
||||
for (int i = 0; i < NR_DOMAINS; i++) {
|
||||
ret = pxa1908_pd_init(ctrl, i, dev);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
|
||||
return of_genpd_add_provider_onecell(dev->parent->of_node, &ctrl->onecell_data);
|
||||
|
||||
err:
|
||||
pxa1908_pd_remove(auxdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct auxiliary_device_id pxa1908_pd_id[] = {
|
||||
{ .name = "clk_pxa1908_apmu.power" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(auxiliary, pxa1908_pd_id);
|
||||
|
||||
static struct auxiliary_driver pxa1908_pd_driver = {
|
||||
.probe = pxa1908_pd_probe,
|
||||
.remove = pxa1908_pd_remove,
|
||||
.id_table = pxa1908_pd_id,
|
||||
};
|
||||
module_auxiliary_driver(pxa1908_pd_driver);
|
||||
|
||||
MODULE_AUTHOR("Duje Mihanović <duje@dujemihanovic.xyz>");
|
||||
MODULE_DESCRIPTION("Marvell PXA1908 power domain driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
Loading…
Reference in New Issue