ti_sci_pm_domains.c
6.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/*
* TI SCI Generic Power Domain Driver
*
* Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com/
* J Keerthy <j-keerthy@ti.com>
* Dave Gerlach <d-gerlach@ti.com>
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/err.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/slab.h>
#include <linux/soc/ti/ti_sci_protocol.h>
#define TI_GENPD_NAME_LENGTH 16
/**
* struct ti_sci_genpd_data: holds data needed for every power domain
* @ti_sci: handle to TI SCI protocol driver that provides ops to
* communicate with system control processor.
* @dev: pointer to dev for the driver for devm allocs
* @pd_list_mutex: Mutex for protecting the global list of power domains
* @pd_list: list that hols all power domains as they are allocated
*/
struct ti_sci_genpd_data {
const struct ti_sci_handle *ti_sci;
struct device *dev;
struct mutex pd_list_mutex; /* Protect master list of domains */
struct list_head pd_list;
};
/**
* struct ti_sci_pm_domain: TI specific data needed for each power domain
* @idx: index of the power domain that identifies it with the system
* control processor.
* @pd: generic_pm_domain for use with the genpd framework
* @node: list_head for tracking all domains in a list
* @parent: pointer to the global data defined for all domains
*/
struct ti_sci_pm_domain {
int idx;
struct generic_pm_domain pd;
struct list_head node;
struct ti_sci_genpd_data *parent;
};
#define genpd_to_ti_sci_pd(gpd) container_of(gpd, struct ti_sci_pm_domain, pd)
/**
* pd_power_off(): generic_pm_domain power_off hook
* @domain: generic_pm_domain struct provided by genpd framework of the
* pd to be shut off
*
* This hook uses the put_device dev_op provided by ti_sci to power off the
* device associated to the power domain provided.
*/
static int pd_power_off(struct generic_pm_domain *domain)
{
struct ti_sci_pm_domain *ti_sci_pd = genpd_to_ti_sci_pd(domain);
const struct ti_sci_handle *ti_sci = ti_sci_pd->parent->ti_sci;
return ti_sci->ops.dev_ops.put_device(ti_sci,
ti_sci_pd->idx);
}
/**
* pd_power_on(): generic_pm_domain power_on hook
* @domain: generic_pm_domain struct provided by genpd framework of the
* pd to be powered on
*
* This hook uses the get_device dev_op provided by ti_sci to power on the
* device associated to the power domain provided.
*/
static int pd_power_on(struct generic_pm_domain *domain)
{
struct ti_sci_pm_domain *ti_sci_pd = genpd_to_ti_sci_pd(domain);
const struct ti_sci_handle *ti_sci = ti_sci_pd->parent->ti_sci;
return ti_sci->ops.dev_ops.get_device(ti_sci,
ti_sci_pd->idx);
}
/**
* of_ti_sci_genpd_xlate_onecell() - Xlate function using a single index.
* @genpdspec: OF phandle args to define an index to be communicated over
* TI SCI to the system control processor to identify it
* @data: xlate function private data - pointer to the ti_sci_genpd_data
* struct containing global sci pm domain data
*
* This is xlate function takes a single cell as an index representing the
* id to be passed to the system control processor. As each device in the
* device tree probes a single pm domain will be created specifically for it
* based on the index passed in the pweor-domains property. If no pm domain
* yet exists for that index it is created, otherwise it is looked up and
* returned. Through this 1 to 1 association between power domains and
* devices the genpd framework will work to directly control devices
* through the pm_runtime framework.
*/
static struct generic_pm_domain *of_ti_sci_genpd_xlate_onecell(
struct of_phandle_args *genpdspec,
void *data)
{
struct ti_sci_genpd_data *ti_sci_genpd = data;
const struct ti_sci_handle *ti_sci = ti_sci_genpd->ti_sci;
struct device *dev = ti_sci_genpd->dev;
unsigned int idx = genpdspec->args[0];
struct ti_sci_pm_domain *ti_sci_pd = NULL, *pd;
char *name;
int ret = 0;
if (genpdspec->args_count != 1)
return ERR_PTR(-EINVAL);
/*
* Check the validity of the requested idx, if the index is not valid
* the PMMC will return a NAK here and we will not allocate it.
*/
ret = ti_sci->ops.dev_ops.is_valid(ti_sci, idx);
if (ret)
return ERR_PTR(-EINVAL);
mutex_lock(&ti_sci_genpd->pd_list_mutex);
list_for_each_entry(pd, &ti_sci_genpd->pd_list, node) {
if (pd->idx == idx) {
ti_sci_pd = pd;
goto unlock_and_return;
}
}
ti_sci_pd = devm_kzalloc(dev,
sizeof(*ti_sci_pd),
GFP_KERNEL);
if (!ti_sci_pd) {
ret = -ENOMEM;
goto unlock_and_return;
}
ti_sci_pd->idx = idx;
ti_sci_pd->pd.power_off = pd_power_off;
ti_sci_pd->pd.power_on = pd_power_on;
name = devm_kzalloc(dev, TI_GENPD_NAME_LENGTH, GFP_KERNEL);
if (!name) {
devm_kfree(dev, ti_sci_pd);
ret = -ENOMEM;
goto unlock_and_return;
}
snprintf(name, TI_GENPD_NAME_LENGTH, "pd-%d", idx);
ti_sci_pd->pd.name = name;
ti_sci_pd->parent = ti_sci_genpd;
/*
* Init each pd as is_off so we always call pd_power_on
* to make sure reference counting is properly maintained
* on the SCI side
*/
pm_genpd_init(&ti_sci_pd->pd, NULL, true);
list_add(&ti_sci_pd->node, &ti_sci_genpd->pd_list);
unlock_and_return:
mutex_unlock(&ti_sci_genpd->pd_list_mutex);
if (ret)
return ERR_PTR(ret);
else
return &ti_sci_pd->pd;
}
static const struct of_device_id ti_sci_pm_domain_matches[] = {
{ .compatible = "ti,sci-pm-domains", },
{ },
};
MODULE_DEVICE_TABLE(of, ti_sci_pm_domain_matches);
static int ti_sci_pm_domains_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct ti_sci_genpd_data *ti_sci_genpd;
ti_sci_genpd = devm_kzalloc(dev, sizeof(*ti_sci_genpd), GFP_KERNEL);
if (!ti_sci_genpd)
return -ENOMEM;
ti_sci_genpd->ti_sci = devm_ti_sci_get_handle(dev);
if (IS_ERR(ti_sci_genpd->ti_sci))
return PTR_ERR(ti_sci_genpd->ti_sci);
ti_sci_genpd->dev = dev;
INIT_LIST_HEAD(&ti_sci_genpd->pd_list);
mutex_init(&ti_sci_genpd->pd_list_mutex);
return __of_genpd_add_provider(np, of_ti_sci_genpd_xlate_onecell,
ti_sci_genpd);
}
static struct platform_driver ti_sci_pm_domains_driver = {
.probe = ti_sci_pm_domains_probe,
.driver = {
.name = "ti_sci_pm_domains",
.of_match_table = ti_sci_pm_domain_matches,
},
};
module_platform_driver(ti_sci_pm_domains_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("TI System Control Interface(SCI) Power Domain driver");
MODULE_AUTHOR("Dave Gerlach");