#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/io.h>
#include "clockdomain.h"
#include "cm.h"
#include "cm33xx.h"
#include "cm-regbits-34xx.h"
#include "cm-regbits-33xx.h"
#include "prm33xx.h"
#if IS_ENABLED(CONFIG_SUSPEND)
#include <linux/suspend.h>
#endif
#define CLKCTRL_IDLEST_FUNCTIONAL 0x0
#define CLKCTRL_IDLEST_INTRANSITION 0x1
#define CLKCTRL_IDLEST_INTERFACE_IDLE 0x2
#define CLKCTRL_IDLEST_DISABLED 0x3
static inline u32 am33xx_cm_read_reg(u16 inst, u16 idx)
{
return readl_relaxed(cm_base.va + inst + idx);
}
static inline void am33xx_cm_write_reg(u32 val, u16 inst, u16 idx)
{
writel_relaxed(val, cm_base.va + inst + idx);
}
static inline u32 am33xx_cm_rmw_reg_bits(u32 mask, u32 bits, s16 inst, s16 idx)
{
u32 v;
v = am33xx_cm_read_reg(inst, idx);
v &= ~mask;
v |= bits;
am33xx_cm_write_reg(v, inst, idx);
return v;
}
static inline u32 am33xx_cm_read_reg_bits(u16 inst, s16 idx, u32 mask)
{
u32 v;
v = am33xx_cm_read_reg(inst, idx);
v &= mask;
v >>= __ffs(mask);
return v;
}
static u32 _clkctrl_idlest(u16 inst, u16 clkctrl_offs)
{
u32 v = am33xx_cm_read_reg(inst, clkctrl_offs);
v &= AM33XX_IDLEST_MASK;
v >>= AM33XX_IDLEST_SHIFT;
return v;
}
static bool _is_module_ready(u16 inst, u16 clkctrl_offs)
{
u32 v;
v = _clkctrl_idlest(inst, clkctrl_offs);
return (v == CLKCTRL_IDLEST_FUNCTIONAL ||
v == CLKCTRL_IDLEST_INTERFACE_IDLE) ? true : false;
}
static void _clktrctrl_write(u8 c, u16 inst, u16 cdoffs)
{
u32 v;
v = am33xx_cm_read_reg(inst, cdoffs);
v &= ~AM33XX_CLKTRCTRL_MASK;
v |= c << AM33XX_CLKTRCTRL_SHIFT;
am33xx_cm_write_reg(v, inst, cdoffs);
}
static bool am33xx_cm_is_clkdm_in_hwsup(u16 inst, u16 cdoffs)
{
u32 v;
v = am33xx_cm_read_reg(inst, cdoffs);
v &= AM33XX_CLKTRCTRL_MASK;
v >>= AM33XX_CLKTRCTRL_SHIFT;
return (v == OMAP34XX_CLKSTCTRL_ENABLE_AUTO) ? true : false;
}
static void am33xx_cm_clkdm_enable_hwsup(u16 inst, u16 cdoffs)
{
_clktrctrl_write(OMAP34XX_CLKSTCTRL_ENABLE_AUTO, inst, cdoffs);
}
static void am33xx_cm_clkdm_disable_hwsup(u16 inst, u16 cdoffs)
{
_clktrctrl_write(OMAP34XX_CLKSTCTRL_DISABLE_AUTO, inst, cdoffs);
}
static void am33xx_cm_clkdm_force_sleep(u16 inst, u16 cdoffs)
{
_clktrctrl_write(OMAP34XX_CLKSTCTRL_FORCE_SLEEP, inst, cdoffs);
}
static void am33xx_cm_clkdm_force_wakeup(u16 inst, u16 cdoffs)
{
_clktrctrl_write(OMAP34XX_CLKSTCTRL_FORCE_WAKEUP, inst, cdoffs);
}
static int am33xx_cm_wait_module_ready(u8 part, s16 inst, u16 clkctrl_offs,
u8 bit_shift)
{
int i = 0;
omap_test_timeout(_is_module_ready(inst, clkctrl_offs),
MAX_MODULE_READY_TIME, i);
return (i < MAX_MODULE_READY_TIME) ? 0 : -EBUSY;
}
static int am33xx_cm_wait_module_idle(u8 part, s16 inst, u16 clkctrl_offs,
u8 bit_shift)
{
int i = 0;
omap_test_timeout((_clkctrl_idlest(inst, clkctrl_offs) ==
CLKCTRL_IDLEST_DISABLED),
MAX_MODULE_READY_TIME, i);
return (i < MAX_MODULE_READY_TIME) ? 0 : -EBUSY;
}
static void am33xx_cm_module_enable(u8 mode, u8 part, u16 inst,
u16 clkctrl_offs)
{
u32 v;
v = am33xx_cm_read_reg(inst, clkctrl_offs);
v &= ~AM33XX_MODULEMODE_MASK;
v |= mode << AM33XX_MODULEMODE_SHIFT;
am33xx_cm_write_reg(v, inst, clkctrl_offs);
}
static void am33xx_cm_module_disable(u8 part, u16 inst, u16 clkctrl_offs)
{
u32 v;
v = am33xx_cm_read_reg(inst, clkctrl_offs);
v &= ~AM33XX_MODULEMODE_MASK;
am33xx_cm_write_reg(v, inst, clkctrl_offs);
}
static int am33xx_clkdm_sleep(struct clockdomain *clkdm)
{
am33xx_cm_clkdm_force_sleep(clkdm->cm_inst, clkdm->clkdm_offs);
return 0;
}
static int am33xx_clkdm_wakeup(struct clockdomain *clkdm)
{
am33xx_cm_clkdm_force_wakeup(clkdm->cm_inst, clkdm->clkdm_offs);
return 0;
}
static void am33xx_clkdm_allow_idle(struct clockdomain *clkdm)
{
am33xx_cm_clkdm_enable_hwsup(clkdm->cm_inst, clkdm->clkdm_offs);
}
static void am33xx_clkdm_deny_idle(struct clockdomain *clkdm)
{
am33xx_cm_clkdm_disable_hwsup(clkdm->cm_inst, clkdm->clkdm_offs);
}
static int am33xx_clkdm_clk_enable(struct clockdomain *clkdm)
{
if (clkdm->flags & CLKDM_CAN_FORCE_WAKEUP)
return am33xx_clkdm_wakeup(clkdm);
return 0;
}
static int am33xx_clkdm_clk_disable(struct clockdomain *clkdm)
{
bool hwsup = false;
#if IS_ENABLED(CONFIG_SUSPEND)
if (pm_suspend_target_state == PM_SUSPEND_STANDBY &&
(clkdm->flags & CLKDM_STANDBY_FORCE_WAKEUP))
return 0;
#endif
hwsup = am33xx_cm_is_clkdm_in_hwsup(clkdm->cm_inst, clkdm->clkdm_offs);
if (!hwsup && (clkdm->flags & CLKDM_CAN_FORCE_SLEEP))
am33xx_clkdm_sleep(clkdm);
return 0;
}
static u32 am33xx_cm_xlate_clkctrl(u8 part, u16 inst, u16 offset)
{
return cm_base.pa + inst + offset;
}
static int am33xx_clkdm_save_context(struct clockdomain *clkdm)
{
clkdm->context = am33xx_cm_read_reg_bits(clkdm->cm_inst,
clkdm->clkdm_offs,
AM33XX_CLKTRCTRL_MASK);
return 0;
}
static int am33xx_clkdm_restore_context(struct clockdomain *clkdm)
{
switch (clkdm->context) {
case OMAP34XX_CLKSTCTRL_DISABLE_AUTO:
am33xx_clkdm_deny_idle(clkdm);
break;
case OMAP34XX_CLKSTCTRL_FORCE_SLEEP:
am33xx_clkdm_sleep(clkdm);
break;
case OMAP34XX_CLKSTCTRL_FORCE_WAKEUP:
am33xx_clkdm_wakeup(clkdm);
break;
case OMAP34XX_CLKSTCTRL_ENABLE_AUTO:
am33xx_clkdm_allow_idle(clkdm);
break;
}
return 0;
}
struct clkdm_ops am33xx_clkdm_operations = {
.clkdm_sleep = am33xx_clkdm_sleep,
.clkdm_wakeup = am33xx_clkdm_wakeup,
.clkdm_allow_idle = am33xx_clkdm_allow_idle,
.clkdm_deny_idle = am33xx_clkdm_deny_idle,
.clkdm_clk_enable = am33xx_clkdm_clk_enable,
.clkdm_clk_disable = am33xx_clkdm_clk_disable,
.clkdm_save_context = am33xx_clkdm_save_context,
.clkdm_restore_context = am33xx_clkdm_restore_context,
};
static const struct cm_ll_data am33xx_cm_ll_data = {
.wait_module_ready = &am33xx_cm_wait_module_ready,
.wait_module_idle = &am33xx_cm_wait_module_idle,
.module_enable = &am33xx_cm_module_enable,
.module_disable = &am33xx_cm_module_disable,
.xlate_clkctrl = &am33xx_cm_xlate_clkctrl,
};
int __init am33xx_cm_init(const struct omap_prcm_init_data *data)
{
return cm_register(&am33xx_cm_ll_data);
}
static void __exit am33xx_cm_exit(void)
{
cm_unregister(&am33xx_cm_ll_data);
}
__exitcall(am33xx_cm_exit);