Merge tag 'armsoc-dt' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc
[sfrench/cifs-2.6.git] / arch / arm / mach-omap2 / omap_hwmod.c
index baadddf9aad49eb44d9d0974e6b6e11c28fd661c..405ac24def05f60558f27b3700f038fdad09d3ad 100644 (file)
 #include "soc.h"
 #include "common.h"
 #include "clockdomain.h"
+#include "hdq1w.h"
+#include "mmc.h"
 #include "powerdomain.h"
 #include "cm2xxx.h"
 #include "cm3xxx.h"
 #include "prm33xx.h"
 #include "prminst44xx.h"
 #include "pm.h"
+#include "wd_timer.h"
 
 /* Name of the OMAP hwmod for the MPU */
 #define MPU_INITIATOR_NAME             "mpu"
@@ -204,6 +207,20 @@ struct clkctrl_provider {
 
 static LIST_HEAD(clkctrl_providers);
 
+/**
+ * struct omap_hwmod_reset - IP specific reset functions
+ * @match: string to match against the module name
+ * @len: number of characters to match
+ * @reset: IP specific reset function
+ *
+ * Used only in cases where struct omap_hwmod is dynamically allocated.
+ */
+struct omap_hwmod_reset {
+       const char *match;
+       int len;
+       int (*reset)(struct omap_hwmod *oh);
+};
+
 /**
  * struct omap_hwmod_soc_ops - fn ptrs for some SoC-specific operations
  * @enable_module: function to enable a module (via MODULEMODE)
@@ -235,6 +252,7 @@ static struct omap_hwmod_soc_ops soc_ops;
 
 /* omap_hwmod_list contains all registered struct omap_hwmods */
 static LIST_HEAD(omap_hwmod_list);
+static DEFINE_MUTEX(list_lock);
 
 /* mpu_oh: used to add/remove MPU initiator from sleepdep list */
 static struct omap_hwmod *mpu_oh;
@@ -2465,7 +2483,7 @@ static void _setup_iclk_autoidle(struct omap_hwmod *oh)
  */
 static int _setup_reset(struct omap_hwmod *oh)
 {
-       int r;
+       int r = 0;
 
        if (oh->_state != _HWMOD_STATE_INITIALIZED)
                return -EINVAL;
@@ -2624,7 +2642,7 @@ static int _setup(struct omap_hwmod *oh, void *data)
  * that the copy process would be relatively complex due to the large number
  * of substructures.
  */
-static int __init _register(struct omap_hwmod *oh)
+static int _register(struct omap_hwmod *oh)
 {
        if (!oh || !oh->name || !oh->class || !oh->class->name ||
            (oh->_state != _HWMOD_STATE_UNKNOWN))
@@ -2663,7 +2681,7 @@ static int __init _register(struct omap_hwmod *oh)
  * locking in this code.  Changes to this assumption will require
  * additional locking.  Returns 0.
  */
-static int __init _add_link(struct omap_hwmod_ocp_if *oi)
+static int _add_link(struct omap_hwmod_ocp_if *oi)
 {
        pr_debug("omap_hwmod: %s -> %s: adding link\n", oi->master->name,
                 oi->slave->name);
@@ -3241,9 +3259,10 @@ static int omap_hwmod_init_regbits(struct device *dev,
  * @sysc_offs: sysc register offset
  * @syss_offs: syss register offset
  */
-int omap_hwmod_init_reg_offs(struct device *dev,
-                            const struct ti_sysc_module_data *data,
-                            s32 *rev_offs, s32 *sysc_offs, s32 *syss_offs)
+static int omap_hwmod_init_reg_offs(struct device *dev,
+                                   const struct ti_sysc_module_data *data,
+                                   s32 *rev_offs, s32 *sysc_offs,
+                                   s32 *syss_offs)
 {
        *rev_offs = -ENODEV;
        *sysc_offs = 0;
@@ -3267,9 +3286,9 @@ int omap_hwmod_init_reg_offs(struct device *dev,
  * @data: module data
  * @sysc_flags: module configuration
  */
-int omap_hwmod_init_sysc_flags(struct device *dev,
-                              const struct ti_sysc_module_data *data,
-                              u32 *sysc_flags)
+static int omap_hwmod_init_sysc_flags(struct device *dev,
+                                     const struct ti_sysc_module_data *data,
+                                     u32 *sysc_flags)
 {
        *sysc_flags = 0;
 
@@ -3341,9 +3360,9 @@ int omap_hwmod_init_sysc_flags(struct device *dev,
  * @data: module data
  * @idlemodes: module supported idle modes
  */
-int omap_hwmod_init_idlemodes(struct device *dev,
-                             const struct ti_sysc_module_data *data,
-                             u32 *idlemodes)
+static int omap_hwmod_init_idlemodes(struct device *dev,
+                                    const struct ti_sysc_module_data *data,
+                                    u32 *idlemodes)
 {
        *idlemodes = 0;
 
@@ -3434,14 +3453,18 @@ static int omap_hwmod_check_module(struct device *dev,
  *
  * Note that the allocations here cannot use devm as ti-sysc can rebind.
  */
-int omap_hwmod_allocate_module(struct device *dev, struct omap_hwmod *oh,
-                              const struct ti_sysc_module_data *data,
-                              struct sysc_regbits *sysc_fields,
-                              s32 rev_offs, s32 sysc_offs, s32 syss_offs,
-                              u32 sysc_flags, u32 idlemodes)
+static int omap_hwmod_allocate_module(struct device *dev, struct omap_hwmod *oh,
+                                     const struct ti_sysc_module_data *data,
+                                     struct sysc_regbits *sysc_fields,
+                                     s32 rev_offs, s32 sysc_offs,
+                                     s32 syss_offs, u32 sysc_flags,
+                                     u32 idlemodes)
 {
        struct omap_hwmod_class_sysconfig *sysc;
-       struct omap_hwmod_class *class;
+       struct omap_hwmod_class *class = NULL;
+       struct omap_hwmod_ocp_if *oi = NULL;
+       struct clockdomain *clkdm = NULL;
+       struct clk *clk = NULL;
        void __iomem *regs = NULL;
        unsigned long flags;
 
@@ -3465,26 +3488,128 @@ int omap_hwmod_allocate_module(struct device *dev, struct omap_hwmod *oh,
        }
 
        /*
-        * We need new oh->class as the other devices in the same class
+        * We may need a new oh->class as the other devices in the same class
         * may not yet have ioremapped their registers.
         */
-       class = kmemdup(oh->class, sizeof(*oh->class), GFP_KERNEL);
-       if (!class)
-               return -ENOMEM;
+       if (oh->class->name && strcmp(oh->class->name, data->name)) {
+               class = kmemdup(oh->class, sizeof(*oh->class), GFP_KERNEL);
+               if (!class)
+                       return -ENOMEM;
+       }
 
-       class->sysc = sysc;
+       if (list_empty(&oh->slave_ports)) {
+               oi = kcalloc(1, sizeof(*oi), GFP_KERNEL);
+               if (!oi)
+                       return -ENOMEM;
+
+               /*
+                * Note that we assume interconnect interface clocks will be
+                * managed by the interconnect driver for OCPIF_SWSUP_IDLE case
+                * on omap24xx and omap3.
+                */
+               oi->slave = oh;
+               oi->user = OCP_USER_MPU | OCP_USER_SDMA;
+       }
+
+       if (!oh->_clk) {
+               struct clk_hw_omap *hwclk;
+
+               clk = of_clk_get_by_name(dev->of_node, "fck");
+               if (!IS_ERR(clk))
+                       clk_prepare(clk);
+               else
+                       clk = NULL;
+
+               /*
+                * Populate clockdomain based on dts clock. It is needed for
+                * clkdm_deny_idle() and clkdm_allow_idle() until we have have
+                * interconnect driver and reset driver capable of blocking
+                * clockdomain idle during reset, enable and idle.
+                */
+               if (clk) {
+                       hwclk = to_clk_hw_omap(__clk_get_hw(clk));
+                       if (hwclk && hwclk->clkdm_name)
+                               clkdm = clkdm_lookup(hwclk->clkdm_name);
+               }
+
+               /*
+                * Note that we assume interconnect driver manages the clocks
+                * and do not need to populate oh->_clk for dynamically
+                * allocated modules.
+                */
+               clk_unprepare(clk);
+               clk_put(clk);
+       }
 
        spin_lock_irqsave(&oh->_lock, flags);
        if (regs)
                oh->_mpu_rt_va = regs;
-       oh->class = class;
+       if (class)
+               oh->class = class;
+       oh->class->sysc = sysc;
+       if (oi)
+               _add_link(oi);
+       if (clkdm)
+               oh->clkdm = clkdm;
        oh->_state = _HWMOD_STATE_INITIALIZED;
+       oh->_postsetup_state = _HWMOD_STATE_DEFAULT;
        _setup(oh, NULL);
        spin_unlock_irqrestore(&oh->_lock, flags);
 
        return 0;
 }
 
+static const struct omap_hwmod_reset omap24xx_reset_quirks[] = {
+       { .match = "msdi", .len = 4, .reset = omap_msdi_reset, },
+};
+
+static const struct omap_hwmod_reset dra7_reset_quirks[] = {
+       { .match = "pcie", .len = 4, .reset = dra7xx_pciess_reset, },
+};
+
+static const struct omap_hwmod_reset omap_reset_quirks[] = {
+       { .match = "dss", .len = 3, .reset = omap_dss_reset, },
+       { .match = "hdq1w", .len = 5, .reset = omap_hdq1w_reset, },
+       { .match = "i2c", .len = 3, .reset = omap_i2c_reset, },
+       { .match = "wd_timer", .len = 8, .reset = omap2_wd_timer_reset, },
+};
+
+static void
+omap_hwmod_init_reset_quirk(struct device *dev, struct omap_hwmod *oh,
+                           const struct ti_sysc_module_data *data,
+                           const struct omap_hwmod_reset *quirks,
+                           int quirks_sz)
+{
+       const struct omap_hwmod_reset *quirk;
+       int i;
+
+       for (i = 0; i < quirks_sz; i++) {
+               quirk = &quirks[i];
+               if (!strncmp(data->name, quirk->match, quirk->len)) {
+                       oh->class->reset = quirk->reset;
+
+                       return;
+               }
+       }
+}
+
+static void
+omap_hwmod_init_reset_quirks(struct device *dev, struct omap_hwmod *oh,
+                            const struct ti_sysc_module_data *data)
+{
+       if (soc_is_omap24xx())
+               omap_hwmod_init_reset_quirk(dev, oh, data,
+                                           omap24xx_reset_quirks,
+                                           ARRAY_SIZE(omap24xx_reset_quirks));
+
+       if (soc_is_dra7xx())
+               omap_hwmod_init_reset_quirk(dev, oh, data, dra7_reset_quirks,
+                                           ARRAY_SIZE(dra7_reset_quirks));
+
+       omap_hwmod_init_reset_quirk(dev, oh, data, omap_reset_quirks,
+                                   ARRAY_SIZE(omap_reset_quirks));
+}
+
 /**
  * omap_hwmod_init_module - initialize new module
  * @dev: struct device
@@ -3505,8 +3630,31 @@ int omap_hwmod_init_module(struct device *dev,
                return -EINVAL;
 
        oh = _lookup(data->name);
-       if (!oh)
-               return -ENODEV;
+       if (!oh) {
+               oh = kzalloc(sizeof(*oh), GFP_KERNEL);
+               if (!oh)
+                       return -ENOMEM;
+
+               oh->name = data->name;
+               oh->_state = _HWMOD_STATE_UNKNOWN;
+               lockdep_register_key(&oh->hwmod_key);
+
+               /* Unused, can be handled by PRM driver handling resets */
+               oh->prcm.omap4.flags = HWMOD_OMAP4_NO_CONTEXT_LOSS_BIT;
+
+               oh->class = kzalloc(sizeof(*oh->class), GFP_KERNEL);
+               if (!oh->class) {
+                       kfree(oh);
+                       return -ENOMEM;
+               }
+
+               omap_hwmod_init_reset_quirks(dev, oh, data);
+
+               oh->class->name = data->name;
+               mutex_lock(&list_lock);
+               error = _register(oh);
+               mutex_unlock(&list_lock);
+       }
 
        cookie->data = oh;
 
@@ -3527,10 +3675,20 @@ int omap_hwmod_init_module(struct device *dev,
        if (error)
                return error;
 
+       if (data->cfg->quirks & SYSC_QUIRK_NO_IDLE)
+               oh->flags |= HWMOD_NO_IDLE;
        if (data->cfg->quirks & SYSC_QUIRK_NO_IDLE_ON_INIT)
                oh->flags |= HWMOD_INIT_NO_IDLE;
        if (data->cfg->quirks & SYSC_QUIRK_NO_RESET_ON_INIT)
                oh->flags |= HWMOD_INIT_NO_RESET;
+       if (data->cfg->quirks & SYSC_QUIRK_USE_CLOCKACT)
+               oh->flags |= HWMOD_SET_DEFAULT_CLOCKACT;
+       if (data->cfg->quirks & SYSC_QUIRK_SWSUP_SIDLE)
+               oh->flags |= HWMOD_SWSUP_SIDLE;
+       if (data->cfg->quirks & SYSC_QUIRK_SWSUP_SIDLE_ACT)
+               oh->flags |= HWMOD_SWSUP_SIDLE_ACT;
+       if (data->cfg->quirks & SYSC_QUIRK_SWSUP_MSTANDBY)
+               oh->flags |= HWMOD_SWSUP_MSTANDBY;
 
        error = omap_hwmod_check_module(dev, oh, data, sysc_fields,
                                        rev_offs, sysc_offs, syss_offs,