|
8 | 8 |
|
9 | 9 | #include <linux/kernel.h>
|
10 | 10 | #include <linux/io.h>
|
| 11 | +#include <linux/platform_device.h> |
11 | 12 | #include <linux/pm_runtime.h>
|
12 | 13 | #include <linux/pm_domain.h>
|
13 | 14 | #include <linux/pm_qos.h>
|
@@ -1933,3 +1934,291 @@ void pm_genpd_init(struct generic_pm_domain *genpd,
|
1933 | 1934 | list_add(&genpd->gpd_list_node, &gpd_list);
|
1934 | 1935 | mutex_unlock(&gpd_list_lock);
|
1935 | 1936 | }
|
| 1937 | + |
| 1938 | +#ifdef CONFIG_PM_GENERIC_DOMAINS_OF |
| 1939 | +/* |
| 1940 | + * Device Tree based PM domain providers. |
| 1941 | + * |
| 1942 | + * The code below implements generic device tree based PM domain providers that |
| 1943 | + * bind device tree nodes with generic PM domains registered in the system. |
| 1944 | + * |
| 1945 | + * Any driver that registers generic PM domains and needs to support binding of |
| 1946 | + * devices to these domains is supposed to register a PM domain provider, which |
| 1947 | + * maps a PM domain specifier retrieved from the device tree to a PM domain. |
| 1948 | + * |
| 1949 | + * Two simple mapping functions have been provided for convenience: |
| 1950 | + * - __of_genpd_xlate_simple() for 1:1 device tree node to PM domain mapping. |
| 1951 | + * - __of_genpd_xlate_onecell() for mapping of multiple PM domains per node by |
| 1952 | + * index. |
| 1953 | + */ |
| 1954 | + |
| 1955 | +/** |
| 1956 | + * struct of_genpd_provider - PM domain provider registration structure |
| 1957 | + * @link: Entry in global list of PM domain providers |
| 1958 | + * @node: Pointer to device tree node of PM domain provider |
| 1959 | + * @xlate: Provider-specific xlate callback mapping a set of specifier cells |
| 1960 | + * into a PM domain. |
| 1961 | + * @data: context pointer to be passed into @xlate callback |
| 1962 | + */ |
| 1963 | +struct of_genpd_provider { |
| 1964 | + struct list_head link; |
| 1965 | + struct device_node *node; |
| 1966 | + genpd_xlate_t xlate; |
| 1967 | + void *data; |
| 1968 | +}; |
| 1969 | + |
| 1970 | +/* List of registered PM domain providers. */ |
| 1971 | +static LIST_HEAD(of_genpd_providers); |
| 1972 | +/* Mutex to protect the list above. */ |
| 1973 | +static DEFINE_MUTEX(of_genpd_mutex); |
| 1974 | + |
| 1975 | +/** |
| 1976 | + * __of_genpd_xlate_simple() - Xlate function for direct node-domain mapping |
| 1977 | + * @genpdspec: OF phandle args to map into a PM domain |
| 1978 | + * @data: xlate function private data - pointer to struct generic_pm_domain |
| 1979 | + * |
| 1980 | + * This is a generic xlate function that can be used to model PM domains that |
| 1981 | + * have their own device tree nodes. The private data of xlate function needs |
| 1982 | + * to be a valid pointer to struct generic_pm_domain. |
| 1983 | + */ |
| 1984 | +struct generic_pm_domain *__of_genpd_xlate_simple( |
| 1985 | + struct of_phandle_args *genpdspec, |
| 1986 | + void *data) |
| 1987 | +{ |
| 1988 | + if (genpdspec->args_count != 0) |
| 1989 | + return ERR_PTR(-EINVAL); |
| 1990 | + return data; |
| 1991 | +} |
| 1992 | +EXPORT_SYMBOL_GPL(__of_genpd_xlate_simple); |
| 1993 | + |
| 1994 | +/** |
| 1995 | + * __of_genpd_xlate_onecell() - Xlate function using a single index. |
| 1996 | + * @genpdspec: OF phandle args to map into a PM domain |
| 1997 | + * @data: xlate function private data - pointer to struct genpd_onecell_data |
| 1998 | + * |
| 1999 | + * This is a generic xlate function that can be used to model simple PM domain |
| 2000 | + * controllers that have one device tree node and provide multiple PM domains. |
| 2001 | + * A single cell is used as an index into an array of PM domains specified in |
| 2002 | + * the genpd_onecell_data struct when registering the provider. |
| 2003 | + */ |
| 2004 | +struct generic_pm_domain *__of_genpd_xlate_onecell( |
| 2005 | + struct of_phandle_args *genpdspec, |
| 2006 | + void *data) |
| 2007 | +{ |
| 2008 | + struct genpd_onecell_data *genpd_data = data; |
| 2009 | + unsigned int idx = genpdspec->args[0]; |
| 2010 | + |
| 2011 | + if (genpdspec->args_count != 1) |
| 2012 | + return ERR_PTR(-EINVAL); |
| 2013 | + |
| 2014 | + if (idx >= genpd_data->num_domains) { |
| 2015 | + pr_err("%s: invalid domain index %u\n", __func__, idx); |
| 2016 | + return ERR_PTR(-EINVAL); |
| 2017 | + } |
| 2018 | + |
| 2019 | + if (!genpd_data->domains[idx]) |
| 2020 | + return ERR_PTR(-ENOENT); |
| 2021 | + |
| 2022 | + return genpd_data->domains[idx]; |
| 2023 | +} |
| 2024 | +EXPORT_SYMBOL_GPL(__of_genpd_xlate_onecell); |
| 2025 | + |
| 2026 | +/** |
| 2027 | + * __of_genpd_add_provider() - Register a PM domain provider for a node |
| 2028 | + * @np: Device node pointer associated with the PM domain provider. |
| 2029 | + * @xlate: Callback for decoding PM domain from phandle arguments. |
| 2030 | + * @data: Context pointer for @xlate callback. |
| 2031 | + */ |
| 2032 | +int __of_genpd_add_provider(struct device_node *np, genpd_xlate_t xlate, |
| 2033 | + void *data) |
| 2034 | +{ |
| 2035 | + struct of_genpd_provider *cp; |
| 2036 | + |
| 2037 | + cp = kzalloc(sizeof(*cp), GFP_KERNEL); |
| 2038 | + if (!cp) |
| 2039 | + return -ENOMEM; |
| 2040 | + |
| 2041 | + cp->node = of_node_get(np); |
| 2042 | + cp->data = data; |
| 2043 | + cp->xlate = xlate; |
| 2044 | + |
| 2045 | + mutex_lock(&of_genpd_mutex); |
| 2046 | + list_add(&cp->link, &of_genpd_providers); |
| 2047 | + mutex_unlock(&of_genpd_mutex); |
| 2048 | + pr_debug("Added domain provider from %s\n", np->full_name); |
| 2049 | + |
| 2050 | + return 0; |
| 2051 | +} |
| 2052 | +EXPORT_SYMBOL_GPL(__of_genpd_add_provider); |
| 2053 | + |
| 2054 | +/** |
| 2055 | + * of_genpd_del_provider() - Remove a previously registered PM domain provider |
| 2056 | + * @np: Device node pointer associated with the PM domain provider |
| 2057 | + */ |
| 2058 | +void of_genpd_del_provider(struct device_node *np) |
| 2059 | +{ |
| 2060 | + struct of_genpd_provider *cp; |
| 2061 | + |
| 2062 | + mutex_lock(&of_genpd_mutex); |
| 2063 | + list_for_each_entry(cp, &of_genpd_providers, link) { |
| 2064 | + if (cp->node == np) { |
| 2065 | + list_del(&cp->link); |
| 2066 | + of_node_put(cp->node); |
| 2067 | + kfree(cp); |
| 2068 | + break; |
| 2069 | + } |
| 2070 | + } |
| 2071 | + mutex_unlock(&of_genpd_mutex); |
| 2072 | +} |
| 2073 | +EXPORT_SYMBOL_GPL(of_genpd_del_provider); |
| 2074 | + |
| 2075 | +/** |
| 2076 | + * of_genpd_get_from_provider() - Look-up PM domain |
| 2077 | + * @genpdspec: OF phandle args to use for look-up |
| 2078 | + * |
| 2079 | + * Looks for a PM domain provider under the node specified by @genpdspec and if |
| 2080 | + * found, uses xlate function of the provider to map phandle args to a PM |
| 2081 | + * domain. |
| 2082 | + * |
| 2083 | + * Returns a valid pointer to struct generic_pm_domain on success or ERR_PTR() |
| 2084 | + * on failure. |
| 2085 | + */ |
| 2086 | +static struct generic_pm_domain *of_genpd_get_from_provider( |
| 2087 | + struct of_phandle_args *genpdspec) |
| 2088 | +{ |
| 2089 | + struct generic_pm_domain *genpd = ERR_PTR(-ENOENT); |
| 2090 | + struct of_genpd_provider *provider; |
| 2091 | + |
| 2092 | + mutex_lock(&of_genpd_mutex); |
| 2093 | + |
| 2094 | + /* Check if we have such a provider in our array */ |
| 2095 | + list_for_each_entry(provider, &of_genpd_providers, link) { |
| 2096 | + if (provider->node == genpdspec->np) |
| 2097 | + genpd = provider->xlate(genpdspec, provider->data); |
| 2098 | + if (!IS_ERR(genpd)) |
| 2099 | + break; |
| 2100 | + } |
| 2101 | + |
| 2102 | + mutex_unlock(&of_genpd_mutex); |
| 2103 | + |
| 2104 | + return genpd; |
| 2105 | +} |
| 2106 | + |
| 2107 | +/** |
| 2108 | + * genpd_dev_pm_detach - Detach a device from its PM domain. |
| 2109 | + * @dev: Device to attach. |
| 2110 | + * @power_off: Currently not used |
| 2111 | + * |
| 2112 | + * Try to locate a corresponding generic PM domain, which the device was |
| 2113 | + * attached to previously. If such is found, the device is detached from it. |
| 2114 | + */ |
| 2115 | +static void genpd_dev_pm_detach(struct device *dev, bool power_off) |
| 2116 | +{ |
| 2117 | + struct generic_pm_domain *pd = NULL, *gpd; |
| 2118 | + int ret = 0; |
| 2119 | + |
| 2120 | + if (!dev->pm_domain) |
| 2121 | + return; |
| 2122 | + |
| 2123 | + mutex_lock(&gpd_list_lock); |
| 2124 | + list_for_each_entry(gpd, &gpd_list, gpd_list_node) { |
| 2125 | + if (&gpd->domain == dev->pm_domain) { |
| 2126 | + pd = gpd; |
| 2127 | + break; |
| 2128 | + } |
| 2129 | + } |
| 2130 | + mutex_unlock(&gpd_list_lock); |
| 2131 | + |
| 2132 | + if (!pd) |
| 2133 | + return; |
| 2134 | + |
| 2135 | + dev_dbg(dev, "removing from PM domain %s\n", pd->name); |
| 2136 | + |
| 2137 | + while (1) { |
| 2138 | + ret = pm_genpd_remove_device(pd, dev); |
| 2139 | + if (ret != -EAGAIN) |
| 2140 | + break; |
| 2141 | + cond_resched(); |
| 2142 | + } |
| 2143 | + |
| 2144 | + if (ret < 0) { |
| 2145 | + dev_err(dev, "failed to remove from PM domain %s: %d", |
| 2146 | + pd->name, ret); |
| 2147 | + return; |
| 2148 | + } |
| 2149 | + |
| 2150 | + /* Check if PM domain can be powered off after removing this device. */ |
| 2151 | + genpd_queue_power_off_work(pd); |
| 2152 | +} |
| 2153 | + |
| 2154 | +/** |
| 2155 | + * genpd_dev_pm_attach - Attach a device to its PM domain using DT. |
| 2156 | + * @dev: Device to attach. |
| 2157 | + * |
| 2158 | + * Parse device's OF node to find a PM domain specifier. If such is found, |
| 2159 | + * attaches the device to retrieved pm_domain ops. |
| 2160 | + * |
| 2161 | + * Both generic and legacy Samsung-specific DT bindings are supported to keep |
| 2162 | + * backwards compatibility with existing DTBs. |
| 2163 | + * |
| 2164 | + * Returns 0 on successfully attached PM domain or negative error code. |
| 2165 | + */ |
| 2166 | +int genpd_dev_pm_attach(struct device *dev) |
| 2167 | +{ |
| 2168 | + struct of_phandle_args pd_args; |
| 2169 | + struct generic_pm_domain *pd; |
| 2170 | + int ret; |
| 2171 | + |
| 2172 | + if (!dev->of_node) |
| 2173 | + return -ENODEV; |
| 2174 | + |
| 2175 | + if (dev->pm_domain) |
| 2176 | + return -EEXIST; |
| 2177 | + |
| 2178 | + ret = of_parse_phandle_with_args(dev->of_node, "power-domains", |
| 2179 | + "#power-domain-cells", 0, &pd_args); |
| 2180 | + if (ret < 0) { |
| 2181 | + if (ret != -ENOENT) |
| 2182 | + return ret; |
| 2183 | + |
| 2184 | + /* |
| 2185 | + * Try legacy Samsung-specific bindings |
| 2186 | + * (for backwards compatibility of DT ABI) |
| 2187 | + */ |
| 2188 | + pd_args.args_count = 0; |
| 2189 | + pd_args.np = of_parse_phandle(dev->of_node, |
| 2190 | + "samsung,power-domain", 0); |
| 2191 | + if (!pd_args.np) |
| 2192 | + return -ENOENT; |
| 2193 | + } |
| 2194 | + |
| 2195 | + pd = of_genpd_get_from_provider(&pd_args); |
| 2196 | + if (IS_ERR(pd)) { |
| 2197 | + dev_dbg(dev, "%s() failed to find PM domain: %ld\n", |
| 2198 | + __func__, PTR_ERR(pd)); |
| 2199 | + of_node_put(dev->of_node); |
| 2200 | + return PTR_ERR(pd); |
| 2201 | + } |
| 2202 | + |
| 2203 | + dev_dbg(dev, "adding to PM domain %s\n", pd->name); |
| 2204 | + |
| 2205 | + while (1) { |
| 2206 | + ret = pm_genpd_add_device(pd, dev); |
| 2207 | + if (ret != -EAGAIN) |
| 2208 | + break; |
| 2209 | + cond_resched(); |
| 2210 | + } |
| 2211 | + |
| 2212 | + if (ret < 0) { |
| 2213 | + dev_err(dev, "failed to add to PM domain %s: %d", |
| 2214 | + pd->name, ret); |
| 2215 | + of_node_put(dev->of_node); |
| 2216 | + return ret; |
| 2217 | + } |
| 2218 | + |
| 2219 | + dev->pm_domain->detach = genpd_dev_pm_detach; |
| 2220 | + |
| 2221 | + return 0; |
| 2222 | +} |
| 2223 | +EXPORT_SYMBOL_GPL(genpd_dev_pm_attach); |
| 2224 | +#endif |
0 commit comments