soc/tegra: Changes for v6.20-rc1

This series primarily refactors the Tegra PMC driver to eliminate
 reliance on a global variable, transitioning to passing the tegra_pmc
 context explicitly across clocks, powergates, sysfs/debugfs interfaces,
 and power management callbacks. Additionally, it resolves a warning
 during system resume by deferring an unsafe generic_handle_irq() call to
 a hard IRQ context using irq_work.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEiOrDCAFJzPfAjcif3SOs138+s6EFAmlskJUACgkQ3SOs138+
 s6EIaQ//WtuuMjXFkxbNPgO3IuRsYuQIswHZouMkRSQJWeWAddRYpO5mE4QIaGYc
 h0pdJp0tnvwO8rMQT06ipCn2UG25WnkqNeRFv1BUKDcrMV19wi4qqONGZXOaeQAW
 EDnGq2ya3laZKstgJbMlpnbD0sAVt1TggH8Dm8ayTvxb5A9kCFnjTsHIGot53i+q
 Nmm46XHKX6noN6Xw3RCUch/CgmfDdga/nSDtrlxV0GNUFphONxA9N9dRQLSBOEt1
 IDOyojJhCMQ9FKMSEJa4q0llkGZBO2U7zMnl7COeMYQIG8riITu5y9OqWZorqpEL
 cpvUZqu8hya3FhJyBkQhbJ/LBniSX6PNMoLdataOXWhW59IBBKr13W5IMKZN0sK2
 82eSSyI7FOF7WwyQ6JxVN31PxCT8/+2qIzv8xblmuGJ3fnkUu5I0R0dH89ukVFCp
 aiDp28bVrGCmEsoUE7Ld7p+CIpnflpm4/oshX6bPkW3EGEQBa+ljU8UrKchNkZaz
 Vb0NM8qORQiR09WuIzWzVcG1J39hAf+8w6QxSXaz92GOclf827XyqQ/ICt222jaN
 XMDUCuw2pBopoIwIzMaH7nFeXY6WHTUWwwOapBfX05diJZUEJ0+RPc+KIOqoQjFG
 i6fyOJfHgAbJV/d/C0ob+xS6/bn1IFFt+WJ4JwPVTM/pYUpFt+k=
 =OVOm
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEo6/YBQwIrVS28WGKmmx57+YAGNkFAmlw8eoACgkQmmx57+YA
 GNkc4g//Uf/rXcaunqiTEhP6NgQ2WFGTpmEo0RVqS70sEWno0koOU1JOUmCohuem
 IKkF2MmO7Dr3l6wqpgYYY+VUgtQQG6gWIEODby4XTS1FM1yK4y+49vafudeQFcA9
 oA0QmneMlsyfXEqVQZv0Xo3nN+Us3xfRT8AXsSh8pIFlugJ2WNeF+lpX2y/0gERm
 RaY/vwSM2w664++ykG1dS+t6gP8+AUumO1wMJJhK7annNDPZKybS6xGdme1Lx5iK
 Y+N3naYRCwXKCb4oXC2eJ8N5QQBAxIgEh6PLKdsODCHjiMpQCvOPZE0LUN66mwaV
 QUbXIs786KKEVt0QW8YGyZ6YptZ/Xeph6jIGBAfM9+69qPFCjQLQTxi/HBwIo4R0
 NL7rhltkUTt3WjqzVuuPQlOyYLB6wn2UHA2EyVT2y1z93gEWSIP5N4MBPHrJ0Not
 hS/625emNDbFMxU3MOx3fsQ6r9hS5mMvDQhbhgRNo+5cLnivtuTNj8CKCcbpYDRS
 2e+DHvEJsq0zcKm+I+Nap7BTNoiJ5mYMXyqiz8KNWmfD+bm7BmiWm7QPsXsDF1h9
 fzzZ1RYDI5bDutC1YdKP64NkSs5iev6IaDS0YTrisajPV5KshLt0vzeX9wUnt+pX
 aoTZK2zTDyc0LiD2KSEk0Srvh6SkApc/zk6nU+jsJVBlEGys7Do=
 =ii5w
 -----END PGP SIGNATURE-----

Merge tag 'tegra-for-6.20-soc' of git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux into soc/drivers

soc/tegra: Changes for v6.20-rc1

This series primarily refactors the Tegra PMC driver to eliminate
reliance on a global variable, transitioning to passing the tegra_pmc
context explicitly across clocks, powergates, sysfs/debugfs interfaces,
and power management callbacks. Additionally, it resolves a warning
during system resume by deferring an unsafe generic_handle_irq() call to
a hard IRQ context using irq_work.

* tag 'tegra-for-6.20-soc' of git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux:
  soc/tegra: pmc: Add PMC contextual functions
  soc/tegra: pmc: Do not rely on global variable
  soc/tegra: pmc: Use driver-private data
  soc/tegra: pmc: Use PMC context embedded in powergates
  soc/tegra: pmc: Pass PMC context as debugfs data
  soc/tegra: pmc: Pass PMC context via sys-off callback data
  soc/tegra: pmc: Embed reboot notifier in PMC context
  soc/tegra: pmc: Store PMC context in clocks
  soc/tegra: pmc: Pass struct tegra_pmc to tegra_powergate_state()
  soc/tegra: pmc: Use contextual data instead of global variable
  soc/tegra: pmc: Fix unsafe generic_handle_irq() call

Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
Arnd Bergmann 2026-01-21 16:34:00 +01:00
commit f8d91cfb93
2 changed files with 388 additions and 105 deletions

View File

@ -28,6 +28,7 @@
#include <linux/iopoll.h>
#include <linux/irqdomain.h>
#include <linux/irq.h>
#include <linux/irq_work.h>
#include <linux/kernel.h>
#include <linux/of_address.h>
#include <linux/of_clk.h>
@ -201,18 +202,20 @@
#define TEGRA_SMC_PMC_WRITE 0xbb
struct pmc_clk {
struct clk_hw hw;
unsigned long offs;
u32 mux_shift;
u32 force_en_shift;
struct clk_hw hw;
struct tegra_pmc *pmc;
unsigned long offs;
u32 mux_shift;
u32 force_en_shift;
};
#define to_pmc_clk(_hw) container_of(_hw, struct pmc_clk, hw)
struct pmc_clk_gate {
struct clk_hw hw;
unsigned long offs;
u32 shift;
struct clk_hw hw;
struct tegra_pmc *pmc;
unsigned long offs;
u32 shift;
};
#define to_pmc_clk_gate(_hw) container_of(_hw, struct pmc_clk_gate, hw)
@ -265,6 +268,17 @@ static const struct pmc_clk_init_data tegra_pmc_clks_data[] = {
},
};
struct tegra_pmc_core_pd {
struct generic_pm_domain genpd;
struct tegra_pmc *pmc;
};
static inline struct tegra_pmc_core_pd *
to_core_pd(struct generic_pm_domain *genpd)
{
return container_of(genpd, struct tegra_pmc_core_pd, genpd);
}
struct tegra_powergate {
struct generic_pm_domain genpd;
struct tegra_pmc *pmc;
@ -467,7 +481,13 @@ struct tegra_pmc {
unsigned long *wake_type_dual_edge_map;
unsigned long *wake_sw_status_map;
unsigned long *wake_cntrl_level_map;
struct notifier_block reboot_notifier;
struct syscore syscore;
/* Pending wake IRQ processing */
struct irq_work wake_work;
u32 *wake_status;
};
static struct tegra_pmc *pmc = &(struct tegra_pmc) {
@ -541,12 +561,7 @@ static void tegra_pmc_scratch_writel(struct tegra_pmc *pmc, u32 value,
writel(value, pmc->scratch + offset);
}
/*
* TODO Figure out a way to call this with the struct tegra_pmc * passed in.
* This currently doesn't work because readx_poll_timeout() can only operate
* on functions that take a single argument.
*/
static inline bool tegra_powergate_state(int id)
static inline bool tegra_powergate_state(struct tegra_pmc *pmc, int id)
{
if (id == TEGRA_POWERGATE_3D && pmc->soc->has_gpu_clamps)
return (tegra_pmc_readl(pmc, GPU_RG_CNTRL) & 0x1) == 0;
@ -598,8 +613,9 @@ static int tegra20_powergate_set(struct tegra_pmc *pmc, unsigned int id,
tegra_pmc_writel(pmc, PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
/* wait for PMC to execute the command */
ret = readx_poll_timeout(tegra_powergate_state, id, status,
status == new_state, 1, 10);
ret = read_poll_timeout(tegra_powergate_state, status,
status == new_state, 1, 10, false,
pmc, id);
} while (ret == -ETIMEDOUT && retries--);
return ret;
@ -631,8 +647,9 @@ static int tegra114_powergate_set(struct tegra_pmc *pmc, unsigned int id,
return err;
/* wait for PMC to execute the command */
err = readx_poll_timeout(tegra_powergate_state, id, status,
status == new_state, 10, 100000);
err = read_poll_timeout(tegra_powergate_state, status,
status == new_state, 10, 100000, false,
pmc, id);
if (err)
return err;
@ -655,7 +672,7 @@ static int tegra_powergate_set(struct tegra_pmc *pmc, unsigned int id,
mutex_lock(&pmc->powergates_lock);
if (tegra_powergate_state(id) == new_state) {
if (tegra_powergate_state(pmc, id) == new_state) {
mutex_unlock(&pmc->powergates_lock);
return 0;
}
@ -940,29 +957,121 @@ static int tegra_genpd_power_off(struct generic_pm_domain *domain)
return err;
}
static void tegra_pmc_put_device(void *data)
{
struct tegra_pmc *pmc = data;
put_device(pmc->dev);
}
static const struct of_device_id tegra_pmc_match[];
static struct tegra_pmc *tegra_pmc_get(struct device *dev)
{
struct platform_device *pdev;
struct device_node *np;
struct tegra_pmc *pmc;
np = of_parse_phandle(dev->of_node, "nvidia,pmc", 0);
if (!np) {
struct device_node *parent = of_node_get(dev->of_node);
while ((parent = of_get_next_parent(parent)) != NULL) {
np = of_find_matching_node(parent, tegra_pmc_match);
if (np)
break;
}
of_node_put(parent);
if (!np)
return ERR_PTR(-ENODEV);
}
pdev = of_find_device_by_node(np);
of_node_put(np);
if (!pdev)
return ERR_PTR(-ENODEV);
pmc = platform_get_drvdata(pdev);
if (!pmc) {
put_device(&pdev->dev);
return ERR_PTR(-EPROBE_DEFER);
}
return pmc;
}
/**
* tegra_powergate_power_on() - power on partition
* tegra_pmc_get() - find the PMC for a given device
* @dev: device for which to find the PMC
*
* Returns a pointer to the PMC on success or an ERR_PTR()-encoded error code
* otherwise.
*/
struct tegra_pmc *devm_tegra_pmc_get(struct device *dev)
{
struct tegra_pmc *pmc;
int err;
pmc = tegra_pmc_get(dev);
if (IS_ERR(pmc))
return pmc;
err = devm_add_action_or_reset(dev, tegra_pmc_put_device, pmc);
if (err < 0)
return ERR_PTR(err);
return pmc;
}
EXPORT_SYMBOL(devm_tegra_pmc_get);
/**
* tegra_pmc_powergate_power_on() - power on partition
* @pmc: power management controller
* @id: partition ID
*/
int tegra_powergate_power_on(unsigned int id)
int tegra_pmc_powergate_power_on(struct tegra_pmc *pmc, unsigned int id)
{
if (!tegra_powergate_is_available(pmc, id))
return -EINVAL;
return tegra_powergate_set(pmc, id, true);
}
EXPORT_SYMBOL(tegra_pmc_powergate_power_on);
/**
* tegra_powergate_power_on() - power on partition
* @id: partition ID
*/
int tegra_powergate_power_on(unsigned int id)
{
return tegra_pmc_powergate_power_on(pmc, id);
}
EXPORT_SYMBOL(tegra_powergate_power_on);
/**
* tegra_pmc_powergate_power_off() - power off partition
* @pmc: power management controller
* @id: partition ID
*/
int tegra_pmc_powergate_power_off(struct tegra_pmc *pmc, unsigned int id)
{
if (!tegra_powergate_is_available(pmc, id))
return -EINVAL;
return tegra_powergate_set(pmc, id, false);
}
EXPORT_SYMBOL(tegra_pmc_powergate_power_off);
/**
* tegra_powergate_power_off() - power off partition
* @id: partition ID
*/
int tegra_powergate_power_off(unsigned int id)
{
if (!tegra_powergate_is_available(pmc, id))
return -EINVAL;
return tegra_powergate_set(pmc, id, false);
return tegra_pmc_powergate_power_off(pmc, id);
}
EXPORT_SYMBOL(tegra_powergate_power_off);
@ -976,32 +1085,45 @@ static int tegra_powergate_is_powered(struct tegra_pmc *pmc, unsigned int id)
if (!tegra_powergate_is_valid(pmc, id))
return -EINVAL;
return tegra_powergate_state(id);
return tegra_powergate_state(pmc, id);
}
/**
* tegra_pmc_powergate_remove_clamping() - remove power clamps for partition
* @pmc: power management controller
* @id: partition ID
*/
int tegra_pmc_powergate_remove_clamping(struct tegra_pmc *pmc, unsigned int id)
{
if (!tegra_powergate_is_available(pmc, id))
return -EINVAL;
return __tegra_powergate_remove_clamping(pmc, id);
}
EXPORT_SYMBOL(tegra_pmc_powergate_remove_clamping);
/**
* tegra_powergate_remove_clamping() - remove power clamps for partition
* @id: partition ID
*/
int tegra_powergate_remove_clamping(unsigned int id)
{
if (!tegra_powergate_is_available(pmc, id))
return -EINVAL;
return __tegra_powergate_remove_clamping(pmc, id);
return tegra_pmc_powergate_remove_clamping(pmc, id);
}
EXPORT_SYMBOL(tegra_powergate_remove_clamping);
/**
* tegra_powergate_sequence_power_up() - power up partition
* tegra_pmc_powergate_sequence_power_up() - power up partition
* @pmc: power management controller
* @id: partition ID
* @clk: clock for partition
* @rst: reset for partition
*
* Must be called with clk disabled, and returns with clk enabled.
*/
int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk,
struct reset_control *rst)
int tegra_pmc_powergate_sequence_power_up(struct tegra_pmc *pmc,
unsigned int id, struct clk *clk,
struct reset_control *rst)
{
struct tegra_powergate *pg;
int err;
@ -1035,6 +1157,21 @@ int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk,
return err;
}
EXPORT_SYMBOL(tegra_pmc_powergate_sequence_power_up);
/**
* tegra_powergate_sequence_power_up() - power up partition
* @id: partition ID
* @clk: clock for partition
* @rst: reset for partition
*
* Must be called with clk disabled, and returns with clk enabled.
*/
int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk,
struct reset_control *rst)
{
return tegra_pmc_powergate_sequence_power_up(pmc, id, clk, rst);
}
EXPORT_SYMBOL(tegra_powergate_sequence_power_up);
/**
@ -1099,7 +1236,8 @@ int tegra_pmc_cpu_remove_clamping(unsigned int cpuid)
return tegra_powergate_remove_clamping(id);
}
static void tegra_pmc_program_reboot_reason(const char *cmd)
static void tegra_pmc_program_reboot_reason(struct tegra_pmc *pmc,
const char *cmd)
{
u32 value;
@ -1123,17 +1261,15 @@ static void tegra_pmc_program_reboot_reason(const char *cmd)
static int tegra_pmc_reboot_notify(struct notifier_block *this,
unsigned long action, void *data)
{
struct tegra_pmc *pmc = container_of(this, struct tegra_pmc,
reboot_notifier);
if (action == SYS_RESTART)
tegra_pmc_program_reboot_reason(data);
tegra_pmc_program_reboot_reason(pmc, data);
return NOTIFY_DONE;
}
static struct notifier_block tegra_pmc_reboot_notifier = {
.notifier_call = tegra_pmc_reboot_notify,
};
static void tegra_pmc_restart(void)
static void tegra_pmc_restart(struct tegra_pmc *pmc)
{
u32 value;
@ -1145,13 +1281,17 @@ static void tegra_pmc_restart(void)
static int tegra_pmc_restart_handler(struct sys_off_data *data)
{
tegra_pmc_restart();
struct tegra_pmc *pmc = data->cb_data;
tegra_pmc_restart(pmc);
return NOTIFY_DONE;
}
static int tegra_pmc_power_off_handler(struct sys_off_data *data)
{
struct tegra_pmc *pmc = data->cb_data;
/*
* Reboot Nexus 7 into special bootloader mode if USB cable is
* connected in order to display battery status and power off.
@ -1161,7 +1301,7 @@ static int tegra_pmc_power_off_handler(struct sys_off_data *data)
const u32 go_to_charger_mode = 0xa5a55a5a;
tegra_pmc_writel(pmc, go_to_charger_mode, PMC_SCRATCH37);
tegra_pmc_restart();
tegra_pmc_restart(pmc);
}
return NOTIFY_DONE;
@ -1169,6 +1309,7 @@ static int tegra_pmc_power_off_handler(struct sys_off_data *data)
static int powergate_show(struct seq_file *s, void *data)
{
struct tegra_pmc *pmc = data;
unsigned int i;
int status;
@ -1377,6 +1518,8 @@ static int
tegra_pmc_core_pd_set_performance_state(struct generic_pm_domain *genpd,
unsigned int level)
{
struct tegra_pmc_core_pd *pd = to_core_pd(genpd);
struct tegra_pmc *pmc = pd->pmc;
struct dev_pm_opp *opp;
int err;
@ -1404,30 +1547,31 @@ tegra_pmc_core_pd_set_performance_state(struct generic_pm_domain *genpd,
static int tegra_pmc_core_pd_add(struct tegra_pmc *pmc, struct device_node *np)
{
struct generic_pm_domain *genpd;
const char *rname[] = { "core", NULL};
struct tegra_pmc_core_pd *pd;
int err;
genpd = devm_kzalloc(pmc->dev, sizeof(*genpd), GFP_KERNEL);
if (!genpd)
pd = devm_kzalloc(pmc->dev, sizeof(*pd), GFP_KERNEL);
if (!pd)
return -ENOMEM;
genpd->name = "core";
genpd->flags = GENPD_FLAG_NO_SYNC_STATE;
genpd->set_performance_state = tegra_pmc_core_pd_set_performance_state;
pd->genpd.name = "core";
pd->genpd.flags = GENPD_FLAG_NO_SYNC_STATE;
pd->genpd.set_performance_state = tegra_pmc_core_pd_set_performance_state;
pd->pmc = pmc;
err = devm_pm_opp_set_regulators(pmc->dev, rname);
if (err)
return dev_err_probe(pmc->dev, err,
"failed to set core OPP regulator\n");
err = pm_genpd_init(genpd, NULL, false);
err = pm_genpd_init(&pd->genpd, NULL, false);
if (err) {
dev_err(pmc->dev, "failed to init core genpd: %d\n", err);
return err;
}
err = of_genpd_add_provider_simple(np, genpd);
err = of_genpd_add_provider_simple(np, &pd->genpd);
if (err) {
dev_err(pmc->dev, "failed to add core genpd: %d\n", err);
goto remove_genpd;
@ -1436,7 +1580,7 @@ static int tegra_pmc_core_pd_add(struct tegra_pmc *pmc, struct device_node *np)
return 0;
remove_genpd:
pm_genpd_remove(genpd);
pm_genpd_remove(&pd->genpd);
return err;
}
@ -1499,7 +1643,7 @@ static void tegra_powergate_remove(struct generic_pm_domain *genpd)
kfree(pg->clks);
set_bit(pg->id, pmc->powergates_available);
set_bit(pg->id, pg->pmc->powergates_available);
kfree(pg);
}
@ -1603,11 +1747,12 @@ static void tegra_io_pad_unprepare(struct tegra_pmc *pmc)
/**
* tegra_io_pad_power_enable() - enable power to I/O pad
* @pmc: power management controller
* @id: Tegra I/O pad ID for which to enable power
*
* Returns: 0 on success or a negative error code on failure.
*/
int tegra_io_pad_power_enable(enum tegra_io_pad id)
int tegra_pmc_io_pad_power_enable(struct tegra_pmc *pmc, enum tegra_io_pad id)
{
const struct tegra_io_pad_soc *pad;
unsigned long request, status;
@ -1642,15 +1787,28 @@ int tegra_io_pad_power_enable(enum tegra_io_pad id)
mutex_unlock(&pmc->powergates_lock);
return err;
}
EXPORT_SYMBOL(tegra_pmc_io_pad_power_enable);
/**
* tegra_io_pad_power_enable() - enable power to I/O pad
* @id: Tegra I/O pad ID for which to enable power
*
* Returns: 0 on success or a negative error code on failure.
*/
int tegra_io_pad_power_enable(enum tegra_io_pad id)
{
return tegra_pmc_io_pad_power_enable(pmc, id);
}
EXPORT_SYMBOL(tegra_io_pad_power_enable);
/**
* tegra_io_pad_power_disable() - disable power to I/O pad
* tegra_pmc_io_pad_power_disable() - disable power to I/O pad
* @pmc: power management controller
* @id: Tegra I/O pad ID for which to disable power
*
* Returns: 0 on success or a negative error code on failure.
*/
int tegra_io_pad_power_disable(enum tegra_io_pad id)
int tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id)
{
const struct tegra_io_pad_soc *pad;
unsigned long request, status;
@ -1685,6 +1843,18 @@ int tegra_io_pad_power_disable(enum tegra_io_pad id)
mutex_unlock(&pmc->powergates_lock);
return err;
}
EXPORT_SYMBOL(tegra_pmc_io_pad_power_disable);
/**
* tegra_io_pad_power_disable() - disable power to I/O pad
* @id: Tegra I/O pad ID for which to disable power
*
* Returns: 0 on success or a negative error code on failure.
*/
int tegra_io_pad_power_disable(enum tegra_io_pad id)
{
return tegra_pmc_io_pad_power_disable(pmc, id);
}
EXPORT_SYMBOL(tegra_io_pad_power_disable);
static int tegra_io_pad_is_powered(struct tegra_pmc *pmc, enum tegra_io_pad id)
@ -1905,6 +2075,50 @@ static int tegra_pmc_parse_dt(struct tegra_pmc *pmc, struct device_node *np)
return 0;
}
/* translate sc7 wake sources back into IRQs to catch edge triggered wakeups */
static void tegra186_pmc_wake_handler(struct irq_work *work)
{
struct tegra_pmc *pmc = container_of(work, struct tegra_pmc, wake_work);
unsigned int i, wake;
for (i = 0; i < pmc->soc->max_wake_vectors; i++) {
unsigned long status = pmc->wake_status[i];
for_each_set_bit(wake, &status, 32) {
irq_hw_number_t hwirq = wake + (i * 32);
struct irq_desc *desc;
unsigned int irq;
irq = irq_find_mapping(pmc->domain, hwirq);
if (!irq) {
dev_warn(pmc->dev,
"No IRQ found for WAKE#%lu!\n",
hwirq);
continue;
}
dev_dbg(pmc->dev,
"Resume caused by WAKE#%lu mapped to IRQ#%u\n",
hwirq, irq);
desc = irq_to_desc(irq);
if (!desc) {
dev_warn(pmc->dev,
"No descriptor found for IRQ#%u\n",
irq);
continue;
}
if (!desc->action || !desc->action->name)
continue;
generic_handle_irq(irq);
}
pmc->wake_status[i] = 0;
}
}
static int tegra_pmc_init(struct tegra_pmc *pmc)
{
if (pmc->soc->max_wake_events > 0) {
@ -1923,6 +2137,18 @@ static int tegra_pmc_init(struct tegra_pmc *pmc)
pmc->wake_cntrl_level_map = bitmap_zalloc(pmc->soc->max_wake_events, GFP_KERNEL);
if (!pmc->wake_cntrl_level_map)
return -ENOMEM;
pmc->wake_status = kcalloc(pmc->soc->max_wake_vectors, sizeof(u32), GFP_KERNEL);
if (!pmc->wake_status)
return -ENOMEM;
/*
* Initialize IRQ work for processing wake IRQs. Must use
* HARD_IRQ variant to run in hard IRQ context on PREEMPT_RT
* because we call generic_handle_irq() which requires hard
* IRQ context.
*/
pmc->wake_work = IRQ_WORK_INIT_HARD(tegra186_pmc_wake_handler);
}
if (pmc->soc->init)
@ -2104,9 +2330,9 @@ static int tegra_io_pad_pinconf_set(struct pinctrl_dev *pctl_dev,
switch (param) {
case PIN_CONFIG_MODE_LOW_POWER:
if (arg)
err = tegra_io_pad_power_disable(pad->id);
err = tegra_pmc_io_pad_power_disable(pmc, pad->id);
else
err = tegra_io_pad_power_enable(pad->id);
err = tegra_pmc_io_pad_power_enable(pmc, pad->id);
if (err)
return err;
break;
@ -2163,6 +2389,7 @@ static int tegra_pmc_pinctrl_init(struct tegra_pmc *pmc)
static ssize_t reset_reason_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tegra_pmc *pmc = dev_get_drvdata(dev);
u32 value;
value = tegra_pmc_readl(pmc, pmc->soc->regs->rst_status);
@ -2180,6 +2407,7 @@ static DEVICE_ATTR_RO(reset_reason);
static ssize_t reset_level_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tegra_pmc *pmc = dev_get_drvdata(dev);
u32 value;
value = tegra_pmc_readl(pmc, pmc->soc->regs->rst_status);
@ -2543,7 +2771,7 @@ static int tegra_pmc_clk_notify_cb(struct notifier_block *nb,
return NOTIFY_OK;
}
static void pmc_clk_fence_udelay(u32 offset)
static void pmc_clk_fence_udelay(struct tegra_pmc *pmc, u32 offset)
{
tegra_pmc_readl(pmc, offset);
/* pmc clk propagation delay 2 us */
@ -2555,7 +2783,7 @@ static u8 pmc_clk_mux_get_parent(struct clk_hw *hw)
struct pmc_clk *clk = to_pmc_clk(hw);
u32 val;
val = tegra_pmc_readl(pmc, clk->offs) >> clk->mux_shift;
val = tegra_pmc_readl(clk->pmc, clk->offs) >> clk->mux_shift;
val &= PMC_CLK_OUT_MUX_MASK;
return val;
@ -2566,11 +2794,11 @@ static int pmc_clk_mux_set_parent(struct clk_hw *hw, u8 index)
struct pmc_clk *clk = to_pmc_clk(hw);
u32 val;
val = tegra_pmc_readl(pmc, clk->offs);
val = tegra_pmc_readl(clk->pmc, clk->offs);
val &= ~(PMC_CLK_OUT_MUX_MASK << clk->mux_shift);
val |= index << clk->mux_shift;
tegra_pmc_writel(pmc, val, clk->offs);
pmc_clk_fence_udelay(clk->offs);
tegra_pmc_writel(clk->pmc, val, clk->offs);
pmc_clk_fence_udelay(clk->pmc, clk->offs);
return 0;
}
@ -2580,26 +2808,27 @@ static int pmc_clk_is_enabled(struct clk_hw *hw)
struct pmc_clk *clk = to_pmc_clk(hw);
u32 val;
val = tegra_pmc_readl(pmc, clk->offs) & BIT(clk->force_en_shift);
val = tegra_pmc_readl(clk->pmc, clk->offs) & BIT(clk->force_en_shift);
return val ? 1 : 0;
}
static void pmc_clk_set_state(unsigned long offs, u32 shift, int state)
static void pmc_clk_set_state(struct tegra_pmc *pmc, unsigned long offs,
u32 shift, int state)
{
u32 val;
val = tegra_pmc_readl(pmc, offs);
val = state ? (val | BIT(shift)) : (val & ~BIT(shift));
tegra_pmc_writel(pmc, val, offs);
pmc_clk_fence_udelay(offs);
pmc_clk_fence_udelay(pmc, offs);
}
static int pmc_clk_enable(struct clk_hw *hw)
{
struct pmc_clk *clk = to_pmc_clk(hw);
pmc_clk_set_state(clk->offs, clk->force_en_shift, 1);
pmc_clk_set_state(clk->pmc, clk->offs, clk->force_en_shift, 1);
return 0;
}
@ -2608,7 +2837,7 @@ static void pmc_clk_disable(struct clk_hw *hw)
{
struct pmc_clk *clk = to_pmc_clk(hw);
pmc_clk_set_state(clk->offs, clk->force_en_shift, 0);
pmc_clk_set_state(clk->pmc, clk->offs, clk->force_en_shift, 0);
}
static const struct clk_ops pmc_clk_ops = {
@ -2640,6 +2869,7 @@ tegra_pmc_clk_out_register(struct tegra_pmc *pmc,
CLK_SET_PARENT_GATE;
pmc_clk->hw.init = &init;
pmc_clk->pmc = pmc;
pmc_clk->offs = offset;
pmc_clk->mux_shift = data->mux_shift;
pmc_clk->force_en_shift = data->force_en_shift;
@ -2650,15 +2880,16 @@ tegra_pmc_clk_out_register(struct tegra_pmc *pmc,
static int pmc_clk_gate_is_enabled(struct clk_hw *hw)
{
struct pmc_clk_gate *gate = to_pmc_clk_gate(hw);
u32 value = tegra_pmc_readl(gate->pmc, gate->offs);
return tegra_pmc_readl(pmc, gate->offs) & BIT(gate->shift) ? 1 : 0;
return value & BIT(gate->shift) ? 1 : 0;
}
static int pmc_clk_gate_enable(struct clk_hw *hw)
{
struct pmc_clk_gate *gate = to_pmc_clk_gate(hw);
pmc_clk_set_state(gate->offs, gate->shift, 1);
pmc_clk_set_state(gate->pmc, gate->offs, gate->shift, 1);
return 0;
}
@ -2667,7 +2898,7 @@ static void pmc_clk_gate_disable(struct clk_hw *hw)
{
struct pmc_clk_gate *gate = to_pmc_clk_gate(hw);
pmc_clk_set_state(gate->offs, gate->shift, 0);
pmc_clk_set_state(gate->pmc, gate->offs, gate->shift, 0);
}
static const struct clk_ops pmc_clk_gate_ops = {
@ -2695,6 +2926,7 @@ tegra_pmc_clk_gate_register(struct tegra_pmc *pmc, const char *name,
init.flags = 0;
gate->hw.init = &init;
gate->pmc = pmc;
gate->offs = offset;
gate->shift = shift;
@ -2858,6 +3090,8 @@ static int tegra_pmc_regmap_init(struct tegra_pmc *pmc)
static void tegra_pmc_reset_suspend_mode(void *data)
{
struct tegra_pmc *pmc = data;
pmc->suspend_mode = TEGRA_SUSPEND_NOT_READY;
}
@ -2880,7 +3114,7 @@ static int tegra_pmc_probe(struct platform_device *pdev)
return err;
err = devm_add_action_or_reset(&pdev->dev, tegra_pmc_reset_suspend_mode,
NULL);
pmc);
if (err)
return err;
@ -2931,8 +3165,10 @@ static int tegra_pmc_probe(struct platform_device *pdev)
* CPU without resetting everything else.
*/
if (pmc->scratch) {
pmc->reboot_notifier.notifier_call = tegra_pmc_reboot_notify;
err = devm_register_reboot_notifier(&pdev->dev,
&tegra_pmc_reboot_notifier);
&pmc->reboot_notifier);
if (err) {
dev_err(&pdev->dev,
"unable to register reboot notifier, %d\n",
@ -2944,7 +3180,8 @@ static int tegra_pmc_probe(struct platform_device *pdev)
err = devm_register_sys_off_handler(&pdev->dev,
SYS_OFF_MODE_RESTART,
SYS_OFF_PRIO_LOW,
tegra_pmc_restart_handler, NULL);
tegra_pmc_restart_handler,
pmc);
if (err) {
dev_err(&pdev->dev, "failed to register sys-off handler: %d\n",
err);
@ -2958,7 +3195,8 @@ static int tegra_pmc_probe(struct platform_device *pdev)
err = devm_register_sys_off_handler(&pdev->dev,
SYS_OFF_MODE_POWER_OFF,
SYS_OFF_PRIO_FIRMWARE,
tegra_pmc_power_off_handler, NULL);
tegra_pmc_power_off_handler,
pmc);
if (err) {
dev_err(&pdev->dev, "failed to register sys-off handler: %d\n",
err);
@ -3024,7 +3262,7 @@ static int tegra_pmc_probe(struct platform_device *pdev)
if (pmc->soc->set_wake_filters)
pmc->soc->set_wake_filters(pmc);
debugfs_create_file("powergate", 0444, NULL, NULL, &powergate_fops);
debugfs_create_file("powergate", 0444, NULL, pmc, &powergate_fops);
return 0;
@ -3129,47 +3367,33 @@ static void wke_clear_wake_status(struct tegra_pmc *pmc)
}
}
/* translate sc7 wake sources back into IRQs to catch edge triggered wakeups */
static void tegra186_pmc_process_wake_events(struct tegra_pmc *pmc, unsigned int index,
unsigned long status)
{
unsigned int wake;
dev_dbg(pmc->dev, "Wake[%d:%d] status=%#lx\n", (index * 32) + 31, index * 32, status);
for_each_set_bit(wake, &status, 32) {
irq_hw_number_t hwirq = wake + 32 * index;
struct irq_desc *desc;
unsigned int irq;
irq = irq_find_mapping(pmc->domain, hwirq);
desc = irq_to_desc(irq);
if (!desc || !desc->action || !desc->action->name) {
dev_dbg(pmc->dev, "Resume caused by WAKE%ld, IRQ %d\n", hwirq, irq);
continue;
}
dev_dbg(pmc->dev, "Resume caused by WAKE%ld, %s\n", hwirq, desc->action->name);
generic_handle_irq(irq);
}
}
static void tegra186_pmc_wake_syscore_resume(void *data)
{
u32 status, mask;
struct tegra_pmc *pmc = data;
unsigned int i;
u32 mask;
for (i = 0; i < pmc->soc->max_wake_vectors; i++) {
mask = readl(pmc->wake + WAKE_AOWAKE_TIER2_ROUTING(i));
status = readl(pmc->wake + WAKE_AOWAKE_STATUS_R(i)) & mask;
tegra186_pmc_process_wake_events(pmc, i, status);
pmc->wake_status[i] = readl(pmc->wake + WAKE_AOWAKE_STATUS_R(i)) & mask;
}
/* Schedule IRQ work to process wake IRQs (if any) */
irq_work_queue(&pmc->wake_work);
}
static int tegra186_pmc_wake_syscore_suspend(void *data)
{
struct tegra_pmc *pmc = data;
unsigned int i;
/* Check if there are unhandled wake IRQs */
for (i = 0; i < pmc->soc->max_wake_vectors; i++)
if (pmc->wake_status[i])
dev_warn(pmc->dev,
"Unhandled wake IRQs pending vector[%u]: 0x%x\n",
i, pmc->wake_status[i]);
wke_read_sw_wake_status(pmc);
/* flip the wakeup trigger for dual-edge triggered pads
@ -3843,6 +4067,7 @@ static const struct tegra_pmc_regs tegra186_pmc_regs = {
static void tegra186_pmc_init(struct tegra_pmc *pmc)
{
pmc->syscore.ops = &tegra186_pmc_wake_syscore_ops;
pmc->syscore.data = pmc;
register_syscore(&pmc->syscore);
}

View File

@ -16,6 +16,7 @@
struct clk;
struct reset_control;
struct tegra_pmc;
bool tegra_pmc_cpu_is_powered(unsigned int cpuid);
int tegra_pmc_cpu_power_on(unsigned int cpuid);
@ -149,11 +150,24 @@ enum tegra_io_pad {
};
#ifdef CONFIG_SOC_TEGRA_PMC
struct tegra_pmc *devm_tegra_pmc_get(struct device *dev);
int tegra_pmc_powergate_power_on(struct tegra_pmc *pmc, unsigned int id);
int tegra_pmc_powergate_power_off(struct tegra_pmc *pmc, unsigned int id);
int tegra_pmc_powergate_remove_clamping(struct tegra_pmc *pmc, unsigned int id);
/* Must be called with clk disabled, and returns with clk enabled */
int tegra_pmc_powergate_sequence_power_up(struct tegra_pmc *pmc,
unsigned int id, struct clk *clk,
struct reset_control *rst);
int tegra_pmc_io_pad_power_enable(struct tegra_pmc *pmc, enum tegra_io_pad id);
int tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id);
/* legacy */
int tegra_powergate_power_on(unsigned int id);
int tegra_powergate_power_off(unsigned int id);
int tegra_powergate_remove_clamping(unsigned int id);
/* Must be called with clk disabled, and returns with clk enabled */
int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk,
struct reset_control *rst);
@ -166,6 +180,50 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode);
bool tegra_pmc_core_domain_state_synced(void);
#else
static inline struct tegra_pmc *devm_tegra_pmc_get(struct device *dev)
{
return ERR_PTR(-ENOSYS);
}
static inline int
tegra_pmc_powergate_power_on(struct tegra_pmc *pmc, unsigned int id)
{
return -ENOSYS;
}
static inline int
tegra_pmc_powergate_power_off(struct tegra_pmc *pmc, unsigned int id)
{
return -ENOSYS;
}
static inline int
tegra_pmc_powergate_remove_clamping(struct tegra_pmc *pmc, unsigned int id)
{
return -ENOSYS;
}
/* Must be called with clk disabled, and returns with clk enabled */
static inline int
tegra_pmc_powergate_sequence_power_up(struct tegra_pmc *pmc, unsigned int id,
struct clk *clk,
struct reset_control *rst)
{
return -ENOSYS;
}
static inline int
tegra_pmc_io_pad_power_enable(struct tegra_pmc *pmc, enum tegra_io_pad id)
{
return -ENOSYS;
}
static inline int
tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id)
{
return -ENOSYS;
}
static inline int tegra_powergate_power_on(unsigned int id)
{
return -ENOSYS;