/*
* omap_hwmod implementation for OMAP2/3/4
*
* Copyright (C) 2009-2011 Nokia Corporation
* Copyright (C) 2011-2012 Texas Instruments, Inc.
*
* Paul Walmsley, BenoƮt Cousson, Kevin Hilman
*
* Created in collaboration with (alphabetical order): Thara Gopinath,
* Tony Lindgren, Rajendra Nayak, Vikram Pandita, Sakari Poussa, Anand
* Sawant, Santosh Shilimkar, Richard Woodruff
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Introduction
* ------------
* One way to view an OMAP SoC is as a collection of largely unrelated
* IP blocks connected by interconnects. The IP blocks include
* devices such as ARM processors, audio serial interfaces, UARTs,
* etc. Some of these devices, like the DSP, are created by TI;
* others, like the SGX, largely originate from external vendors. In
* TI's documentation, on-chip devices are referred to as "OMAP
* modules." Some of these IP blocks are identical across several
* OMAP versions. Others are revised frequently.
*
* These OMAP modules are tied together by various interconnects.
* Most of the address and data flow between modules is via OCP-based
* interconnects such as the L3 and L4 buses; but there are other
* interconnects that distribute the hardware clock tree, handle idle
* and reset signaling, supply power, and connect the modules to
* various pads or balls on the OMAP package.
*
* OMAP hwmod provides a consistent way to describe the on-chip
* hardware blocks and their integration into the rest of the chip.
* This description can be automatically generated from the TI
* hardware database. OMAP hwmod provides a standard, consistent API
* to reset, enable, idle, and disable these hardware blocks. And
* hwmod provides a way for other core code, such as the Linux device
* code or the OMAP power management and address space mapping code,
* to query the hardware database.
*
* Using hwmod
* -----------
* Drivers won't call hwmod functions directly. That is done by the
* omap_device code, and in rare occasions, by custom integration code
* in arch/arm/ *omap*. The omap_device code includes functions to
* build a struct platform_device using omap_hwmod data, and that is
* currently how hwmod data is communicated to drivers and to the
* Linux driver model. Most drivers will call omap_hwmod functions only
* indirectly, via pm_runtime*() functions.
*
* From a layering perspective, here is where the OMAP hwmod code
* fits into the kernel software stack:
*
* +-------------------------------+
* | Device driver code |
* | (e.g., drivers/) |
* +-------------------------------+
* | Linux driver model |
* | (platform_device / |
* | platform_driver data/code) |
* +-------------------------------+
* | OMAP core-driver integration |
* |(arch/arm/mach-omap2/devices.c)|
* +-------------------------------+
* | omap_device code |
* | (../plat-omap/omap_device.c) |
* +-------------------------------+
* ----> | omap_hwmod code/data | <-----
* | (../mach-omap2/omap_hwmod*) |
* +-------------------------------+
* | OMAP clock/PRCM/register fns |
* | ({read,write}l_relaxed, clk*) |
* +-------------------------------+
*
* Device drivers should not contain any OMAP-specific code or data in
* them. They should only contain code to operate the IP block that
* the driver is responsible for. This is because these IP blocks can
* also appear in other SoCs, either from TI (such as DaVinci) or from
* other manufacturers; and drivers should be reusable across other
* platforms.
*
* The OMAP hwmod code also will attempt to reset and idle all on-chip
* devices upon boot. The goal here is for the kernel to be
* completely self-reliant and independent from bootloaders. This is
* to ensure a repeatable configuration, both to ensure consistent
* runtime behavior, and to make it easier for others to reproduce
* bugs.
*
* OMAP module activity states
* ---------------------------
* The hwmod code considers modules to be in one of several activity
* states. IP blocks start out in an UNKNOWN state, then once they
* are registered via the hwmod code, proceed to the REGISTERED state.
* Once their clock names are resolved to clock pointers, the module
* enters the CLKS_INITED state; and finally, once the module has been
* reset and the integration registers programmed, the INITIALIZED state
* is entered. The hwmod code will then place the module into either
* the IDLE state to save power, or in the case of a critical system
* module, the ENABLED state.
*
* OMAP core integration code can then call omap_hwmod*() functions
* directly to move the module between the IDLE, ENABLED, and DISABLED
* states, as needed. This is done during both the PM idle loop, and
* in the OMAP core integration code's implementation of the PM runtime
* functions.
*
* References
* ----------
* This is a partial list.
* - OMAP2420 Multimedia Processor Silicon Revision 2.1.1, 2.2 (SWPU064)
* - OMAP2430 Multimedia Device POP Silicon Revision 2.1 (SWPU090)
* - OMAP34xx Multimedia Device Silicon Revision 3.1 (SWPU108)
* - OMAP4430 Multimedia Device Silicon Revision 1.0 (SWPU140)
* - Open Core Protocol Specification 2.2
*
* To do:
* - handle IO mapping
* - bus throughput & module latency measurement code
*
* XXX add tests at the beginning of each function to ensure the hwmod is
* in the appropriate state
* XXX error return values should be checked to ensure that they are
* appropriate
*/
#undef DEBUG
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/bootmem.h>
#include <linux/cpu.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <asm/system_misc.h>
#include "clock.h"
#include "omap_hwmod.h"
#include "soc.h"
#include "common.h"
#include "clockdomain.h"
#include "powerdomain.h"
#include "cm2xxx.h"
#include "cm3xxx.h"
#include "cm33xx.h"
#include "prm.h"
#include "prm3xxx.h"
#include "prm44xx.h"
#include "prm33xx.h"
#include "prminst44xx.h"
#include "mux.h"
#include "pm.h"
/* Name of the OMAP hwmod for the MPU */
#define MPU_INITIATOR_NAME "mpu"
/*
* Number of struct omap_hwmod_link records per struct
* omap_hwmod_ocp_if record (master->slave and slave->master)
*/
#define LINKS_PER_OCP_IF 2
/**
* struct omap_hwmod_soc_ops - fn ptrs for some SoC-specific operations
* @enable_module: function to enable a module (via MODULEMODE)
* @disable_module: function to disable a module (via MODULEMODE)
*
* XXX Eventually this functionality will be hidden inside the PRM/CM
* device drivers. Until then, this should avoid huge blocks of cpu_is_*()
* conditionals in this code.
*/
struct omap_hwmod_soc_ops {
void (*enable_module)(struct omap_hwmod *oh);
int (*disable_module)(struct omap_hwmod *oh);
int (*wait_target_ready)(struct omap_hwmod *oh);
int (*assert_hardreset)(struct omap_hwmod *oh,
struct omap_hwmod_rst_info *ohri);
int (*deassert_hardreset)(struct omap_hwmod *oh,
struct omap_hwmod_rst_info *ohri);
int (*is_hardreset_asserted)(struct omap_hwmod *oh,
struct omap_hwmod_rst_info *ohri);
int (*init_clkdm)(struct omap_hwmod *oh);
void (*update_context_lost)(struct omap_hwmod *oh);
int (*get_context_lost)(struct omap_hwmod *oh);
};
/* soc_ops: adapts the omap_hwmod code to the currently-booted SoC */
static struct omap_hwmod_soc_ops soc_ops;
/* omap_hwmod_list contains all registered struct omap_hwmods */
static LIST_HEAD(omap_hwmod_list);
/* mpu_oh: used to add/remove MPU initiator from sleepdep list */
static struct omap_hwmod *mpu_oh;
/* io_chain_lock: used to serialize reconfigurations of the I/O chain */
static DEFINE_SPINLOCK(io_chain_lock);
/*
* linkspace: ptr to a buffer that struct omap_hwmod_link records are
* allocated from - used to reduce the number of small memory
* allocations, which has a significant impact on performance
*/
static struct omap_hwmod_link *linkspace;
/*
* free_ls, max_ls: array indexes into linkspace; representing the
* next free struct omap_hwmod_link index, and the maximum number of
* struct omap_hwmod_link records allocated (respectively)
*/
static unsigned short free_ls, max_ls, ls_supp;
/* inited: set to true once the hwmod code is initialized */
static bool inited;
/* Private functions */
/**
* _fetch_next_ocp_if - return the next OCP interface in a list
* @p: ptr to a ptr to the list_head inside the ocp_if to return
* @i: pointer to the index of the element pointed to by @p in the list
*
* Return a pointer to the struct omap_hwmod_ocp_if record
* containing the struct list_head pointed to by @p, and increment
* @p such that a future call to this routine will return the next
* record.
*/
static struct omap_hwmod_ocp_if *_fetch_next_ocp_if(struct list_head **p,
int *i)
{
struct omap_hwmod_ocp_if *oi;
oi = list_entry(*p, struct omap_hwmod_link, node)->ocp_if;
*p = (*p)->next;
*i = *i + 1;
return oi;
}
/**
* _update_sysc_cache - return the module OCP_SYSCONFIG register, keep copy
* @oh: struct omap_hwmod *
*
* Load the current value of the hwmod OCP_SYSCONFIG register into the
* struct omap_hwmod for later use. Returns -EINVAL if the hwmod has no
* OCP_SYSCONFIG register or 0 upon success.
*/
static int _update_sysc_cache(struct omap_hwmod *oh)
{
if (!oh->class->sysc) {
WARN(1, "omap_hwmod: %s: cannot read OCP_SYSCONFIG: not defined on hwmod's class\n", oh->name);
return -EINVAL;
}
/* XXX ensure module interface clock is up */
oh->_sysc_cache = omap_hwmod_read(oh, oh->class->sysc->sysc_offs);
if (!(oh->class->sysc->sysc_flags & SYSC_NO_CACHE))
oh->_int_flags |= _HWMOD_SYSCONFIG_LOADED;
return 0;
}
/**
* _write_sysconfig - write a value to the module's OCP_SYSCONFIG register
* @v: OCP_SYSCONFIG value to write
* @oh: struct omap_hwmod *
*
* Write @v into the module class' OCP_SYSCONFIG register, if it has
* one. No return value.
*/
static void _write_sysconfig(u32 v, struct omap_hwmod *oh)
{
if (!oh->class->sysc) {
WARN(1, "omap_hwmod: %s: cannot write OCP_SYSCONFIG: not defined on hwmod's class\n", oh->name);
return;
}
/* XXX ensure module interface clock is up */
/* Module might have lost context, always update cache and register */
oh->_sysc_cache = v;
omap_hwmod_write(v, oh, oh->class->sysc->sysc_offs);
}
/**
* _set_master_standbymode: set the OCP_SYSCONFIG MIDLEMODE field in @v
* @oh: struct omap_hwmod *
* @standbymode: MIDLEMODE field bits
* @v: pointer to register contents to modify
*
* Update the master standby mode bits in @v to be @standbymode for
* the @oh hwmod. Does not write to the hardware. Returns -EINVAL
* upon error or 0 upon success.
*/
static int _set_master_standbymode(struct omap_hwmod *oh, u8 standbymode,
u32 *v)
{
u32 mstandby_mask;
u8 mstandby_shift;
if (!oh->class->sysc ||
!(oh->class->sysc->sysc_flags & SYSC_HAS_MIDLEMODE))
return -EINVAL;
if (!oh->class->sysc->sysc_fields) {
WARN(1, "omap_hwmod: %s: offset struct for sysconfig not provided in class\n", oh->name);
return -EINVAL;
}
mstandby_shift = oh->class->sysc->sysc_fields->midle_shift;
mstandby_mask = (0x3 << mstandby_shift);
*v &= ~mstandby_mask;
*v |= __ffs(standbymode) << mstandby_shift;
return 0;
}
/**
* _set_slave_idlemode: set the OCP_SYSCONFIG SIDLEMODE field in @v
* @oh: struct omap_hwmod *
* @idlemode: SIDLEMODE field bits
* @v: pointer to register contents to modify
*
* Update the slave idle mode bits in @v to be @idlemode for the @oh
* hwmod. Does not write to the hardware. Returns -EINVAL upon error
* or 0 upon success.
*/
static int _set_slave_idlemode(struct omap_hwmod *oh, u8 idlemode, u32 *v)
{
u32 sidle_mask;
u8 sidle_shift;
if (!oh->class->sysc ||
!(oh->class->sysc->sysc_flags & SYSC_HAS_SIDLEMODE))
return -EINVAL;
if (!oh->class->sysc->sysc_fields) {
WARN(1, "omap_hwmod: %s: offset struct for sysconfig not provided in class\n", oh->name);
return -EINVAL;
}
sidle_shift = oh->class->sysc->sysc_fields->sidle_shift;
sidle_mask = (0x3 << sidle_shift);
*v &= ~sidle_mask;
*v |= __ffs(idlemode) << sidle_shift;
return 0;
}
/**
* _set_clockactivity: set OCP_SYSCONFIG.CLOCKACTIVITY bits in @v
* @oh: struct omap_hwmod *
* @clockact: CLOCKACTIVITY field bits
* @v: pointer to register contents to modify
*
* Update the clockactivity mode bits in @v to be @clockact for the
* @oh hwmod. Used for additional powersaving on some modules. Does
* not write to the hardware. Returns -EINVAL upon error or 0 upon
* success.
*/
static int _set_clockactivity(struct omap_hwmod *oh, u8 clockact, u32 *v)
{
u32 clkact_mask;
u8 clkact_shift;
if (!oh->class->sysc ||
!(oh->class->sysc->sysc_flags & SYSC_HAS_CLOCKACTIVITY))
return -EINVAL;
if (!oh->class->sysc->sysc_fields) {
WARN(1, "omap_hwmod: %s: offset struct for sysconfig not provided in class\n", oh->name);
return -EINVAL;
}
clkact_shift = oh->class->sysc->sysc_fields->clkact_shift;
clkact_mask = (0x3 << clkact_shift);
*v &= ~clkact_mask;
*v |= clockact << clkact_shift;
return 0;
}
/**
* _set_softreset: set OCP_SYSCONFIG.SOFTRESET bit in @v
* @oh: struct omap_hwmod *
* @v: pointer to register contents to modify
*
* Set the SOFTRESET bit in @v for hwmod @oh. Returns -EINVAL upon
* error or 0 upon success.
*/
static int _set_softreset(struct omap_hwmod *oh, u32 *v)
{
u32 softrst_mask;
if (!oh->class->sysc ||
!(oh->class->sysc->sysc_flags & SYSC_HAS_SOFTRESET))
return -EINVAL;
if (!oh->class->sysc->sysc_fields) {
WARN(1, "omap_hwmod: %s: offset struct for sysconfig not provided in class\n", oh->name);
return -EINVAL;
}
softrst_mask = (0x1 << oh->class->sysc->sysc_fields->srst_shift);
*v |= softrst_mask;
return 0;
}
/**
* _clear_softreset: clear OCP_SYSCONFIG.SOFTRESET bit in @v
* @oh: struct omap_hwmod *
* @v: pointer to register contents to modify
*
* Clear the SOFTRESET bit in @v for hwmod @oh. Returns -EINVAL upon
* error or 0 upon success.
*/
static int _clear_softreset(struct omap_hwmod *oh, u32 *v)
{
u32 softrst_mask;
if (!oh->class->sysc ||
!(oh->class->sysc->sysc_flags & SYSC_HAS_SOFTRESET))
return -EINVAL;
if (!oh->class->sysc->sysc_fields) {
WARN(1,
"omap_hwmod: %s: sysc_fields absent for sysconfig class\n",
oh->name);
return -EINVAL;
}
softrst_mask = (0x1 << oh->class->sysc->sysc_fields->srst_shift);
*v &= ~softrst_mask;
return 0;
}
/**
* _wait_softreset_complete - wait for an OCP softreset to complete
* @oh: struct omap_hwmod * to wait on
*
* Wait until the IP block represented by @oh reports that its OCP
* softreset is complete. This can be triggered by software (see
* _ocp_softreset()) or by hardware upon returning from off-mode (one
* example is HSMMC). Waits for up to MAX_MODULE_SOFTRESET_WAIT
* microseconds. Returns the number of microseconds waited.
*/
static int _wait_softreset_complete(struct omap_hwmod *oh)
{
struct omap_hwmod_class_sysconfig *sysc;
u32 softrst_mask;
int c = 0;
sysc = oh->class->sysc;
if (sysc->sysc_flags & SYSS_HAS_RESET_STATUS)
omap_test_timeout((omap_hwmod_read(oh, sysc->syss_offs)
& SYSS_RESETDONE_MASK),
MAX_MODULE_SOFTRESET_WAIT, c);
else if (sysc->sysc_flags & SYSC_HAS_RESET_STATUS) {
softrst_mask = (0x1 << sysc->sysc_fields->srst_shift);
omap_test_timeout(!(omap_hwmod_read(oh, sysc->sysc_offs)
& softrst_mask),
MAX_MODULE_SOFTRESET_WAIT, c);
}
return c;
}
/**
* _set_dmadisable: set OCP_SYSCONFIG.DMADISABLE bit in @v
* @oh: struct omap_hwmod *
*
* The DMADISABLE bit is a semi-automatic bit present in sysconfig register
* of some modules. When the DMA must perform read/write accesses, the
* DMADISABLE bit is cleared by the hardware. But when the DMA must stop
* for power management, software must set the DMADISABLE bit back to 1.
*
* Set the DMADISABLE bit in @v for hwmod @oh. Returns -EINVAL upon
* error or 0 upon success.
*/
static int _set_dmadisable(struct omap_hwmod *oh)
{
u32 v;
u32 dmadisable_mask;
if (!oh->class->sysc ||
!(oh->class->sysc->sysc_flags & SYSC_HAS_DMADISABLE))
return -EINVAL;
if (!oh->class->sysc->sysc_fields) {
WARN(1, "omap_hwmod: %s: offset struct for sysconfig not provided in class\n", oh->name);
return -EINVAL;
}
/* clocks must be on for this operation */
if (oh->_state != _HWMOD_STATE_ENABLED) {
pr_warn("omap_hwmod: %s: dma can be disabled only from enabled state\n", oh->name);
return -EINVAL;
}
pr_debug("omap_hwmod: %s: setting DMADISABLE\n", oh->name);
v = oh->_sysc_cache;
dmadisable_mask =
(0x1 << oh->class->sysc->sysc_fields->dmadisable_shift);
v |= dmadisable_mask;
_write_sysconfig(v, oh);
return 0;
}
/**
* _set_module_autoidle: set the OCP_SYSCONFIG AUTOIDLE field in @v
* @oh: struct omap_hwmod *
* @autoidle: desired AUTOIDLE bitfield value (0 or 1)
* @v: pointer to register contents to modify
*
* Update the module autoidle bit in @v to be @autoidle for the @oh
* hwmod. The autoidle bit controls whether the module can gate
* internal clocks automatically when it isn't doing anything; the
* exact function of this bit varies on a per-module basis. This
* function does not write to the hardware. Returns -EINVAL upon
* error or 0 upon success.
*/
static int _set_module_autoidle(struct omap_hwmod *oh, u8 autoidle,
u32 *v)
{
u32 autoidle_mask;
u8 autoidle_shift;
if (!oh->class->sysc ||
!(oh->class->sysc->sysc_flags & SYSC_HAS_AUTOIDLE))
return -EINVAL;
if (!oh->class->sysc->sysc_fields) {
WARN(1, "omap_hwmod: %s: offset struct for sysconfig not provided in class\n", oh->name);
return -EINVAL;
}
autoidle_shift = oh->class->sysc->sysc_fields->autoidle_shift;
autoidle_mask = (0x1 << autoidle_shift);
*v &= ~autoidle_mask;
*v |= autoidle << autoidle_shift;
return 0;
}
/**
* _set_idle_ioring_wakeup - enable/disable IO pad wakeup on hwmod idle for mux
* @oh: struct omap_hwmod *
* @set_wake: bool value indicating to set (true) or clear (false) wakeup enable
*
* Set or clear the I/O pad wakeup flag in the mux entries for the
* hwmod @oh. This function changes the @oh->mux->pads_dynamic array
|