2
2
/*
3
3
* Xilinx Zynq MPSoC Power Management
4
4
*
5
- * Copyright (C) 2014-2018 Xilinx, Inc.
5
+ * Copyright (C) 2014-2019 Xilinx, Inc.
6
6
*
7
7
* Davorin Mista <[email protected] >
8
8
16
16
#include <linux/suspend.h>
17
17
18
18
#include <linux/firmware/xlnx-zynqmp.h>
19
+ #include <linux/mailbox/zynqmp-ipi-message.h>
20
+
21
+ /**
22
+ * struct zynqmp_pm_work_struct - Wrapper for struct work_struct
23
+ * @callback_work: Work structure
24
+ * @args: Callback arguments
25
+ */
26
+ struct zynqmp_pm_work_struct {
27
+ struct work_struct callback_work ;
28
+ u32 args [CB_ARG_CNT ];
29
+ };
30
+
31
+ static struct zynqmp_pm_work_struct * zynqmp_pm_init_suspend_work ;
32
+ static struct mbox_chan * rx_chan ;
33
+ static const struct zynqmp_eemi_ops * eemi_ops ;
19
34
20
35
enum pm_suspend_mode {
21
36
PM_SUSPEND_MODE_FIRST = 0 ,
@@ -31,7 +46,6 @@ static const char *const suspend_modes[] = {
31
46
};
32
47
33
48
static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD ;
34
- static const struct zynqmp_eemi_ops * eemi_ops ;
35
49
36
50
enum pm_api_cb_id {
37
51
PM_INIT_SUSPEND_CB = 30 ,
@@ -68,6 +82,53 @@ static irqreturn_t zynqmp_pm_isr(int irq, void *data)
68
82
return IRQ_HANDLED ;
69
83
}
70
84
85
+ static void ipi_receive_callback (struct mbox_client * cl , void * data )
86
+ {
87
+ struct zynqmp_ipi_message * msg = (struct zynqmp_ipi_message * )data ;
88
+ u32 payload [CB_PAYLOAD_SIZE ];
89
+ int ret ;
90
+
91
+ memcpy (payload , msg -> data , sizeof (msg -> len ));
92
+ /* First element is callback API ID, others are callback arguments */
93
+ if (payload [0 ] == PM_INIT_SUSPEND_CB ) {
94
+ if (work_pending (& zynqmp_pm_init_suspend_work -> callback_work ))
95
+ return ;
96
+
97
+ /* Copy callback arguments into work's structure */
98
+ memcpy (zynqmp_pm_init_suspend_work -> args , & payload [1 ],
99
+ sizeof (zynqmp_pm_init_suspend_work -> args ));
100
+
101
+ queue_work (system_unbound_wq ,
102
+ & zynqmp_pm_init_suspend_work -> callback_work );
103
+
104
+ /* Send NULL message to mbox controller to ack the message */
105
+ ret = mbox_send_message (rx_chan , NULL );
106
+ if (ret )
107
+ pr_err ("IPI ack failed. Error %d\n" , ret );
108
+ }
109
+ }
110
+
111
+ /**
112
+ * zynqmp_pm_init_suspend_work_fn - Initialize suspend
113
+ * @work: Pointer to work_struct
114
+ *
115
+ * Bottom-half of PM callback IRQ handler.
116
+ */
117
+ static void zynqmp_pm_init_suspend_work_fn (struct work_struct * work )
118
+ {
119
+ struct zynqmp_pm_work_struct * pm_work =
120
+ container_of (work , struct zynqmp_pm_work_struct , callback_work );
121
+
122
+ if (pm_work -> args [0 ] == SUSPEND_SYSTEM_SHUTDOWN ) {
123
+ orderly_poweroff (true);
124
+ } else if (pm_work -> args [0 ] == SUSPEND_POWER_REQUEST ) {
125
+ pm_suspend (PM_SUSPEND_MEM );
126
+ } else {
127
+ pr_err ("%s Unsupported InitSuspendCb reason code %d.\n" ,
128
+ __func__ , pm_work -> args [0 ]);
129
+ }
130
+ }
131
+
71
132
static ssize_t suspend_mode_show (struct device * dev ,
72
133
struct device_attribute * attr , char * buf )
73
134
{
@@ -119,6 +180,7 @@ static int zynqmp_pm_probe(struct platform_device *pdev)
119
180
{
120
181
int ret , irq ;
121
182
u32 pm_api_version ;
183
+ struct mbox_client * client ;
122
184
123
185
eemi_ops = zynqmp_pm_get_eemi_ops ();
124
186
if (IS_ERR (eemi_ops ))
@@ -134,17 +196,46 @@ static int zynqmp_pm_probe(struct platform_device *pdev)
134
196
if (pm_api_version < ZYNQMP_PM_VERSION )
135
197
return - ENODEV ;
136
198
137
- irq = platform_get_irq (pdev , 0 );
138
- if (irq <= 0 )
139
- return - ENXIO ;
140
-
141
- ret = devm_request_threaded_irq (& pdev -> dev , irq , NULL , zynqmp_pm_isr ,
142
- IRQF_NO_SUSPEND | IRQF_ONESHOT ,
143
- dev_name (& pdev -> dev ), & pdev -> dev );
144
- if (ret ) {
145
- dev_err (& pdev -> dev , "devm_request_threaded_irq '%d' failed "
146
- "with %d\n" , irq , ret );
147
- return ret ;
199
+ if (of_find_property (pdev -> dev .of_node , "mboxes" , NULL )) {
200
+ zynqmp_pm_init_suspend_work =
201
+ devm_kzalloc (& pdev -> dev ,
202
+ sizeof (struct zynqmp_pm_work_struct ),
203
+ GFP_KERNEL );
204
+ if (!zynqmp_pm_init_suspend_work )
205
+ return - ENOMEM ;
206
+
207
+ INIT_WORK (& zynqmp_pm_init_suspend_work -> callback_work ,
208
+ zynqmp_pm_init_suspend_work_fn );
209
+ client = devm_kzalloc (& pdev -> dev , sizeof (* client ), GFP_KERNEL );
210
+ if (!client )
211
+ return - ENOMEM ;
212
+
213
+ client -> dev = & pdev -> dev ;
214
+ client -> rx_callback = ipi_receive_callback ;
215
+
216
+ rx_chan = mbox_request_channel_byname (client , "rx" );
217
+ if (IS_ERR (rx_chan )) {
218
+ dev_err (& pdev -> dev , "Failed to request rx channel\n" );
219
+ return IS_ERR (rx_chan );
220
+ }
221
+ } else if (of_find_property (pdev -> dev .of_node , "interrupts" , NULL )) {
222
+ irq = platform_get_irq (pdev , 0 );
223
+ if (irq <= 0 )
224
+ return - ENXIO ;
225
+
226
+ ret = devm_request_threaded_irq (& pdev -> dev , irq , NULL ,
227
+ zynqmp_pm_isr ,
228
+ IRQF_NO_SUSPEND | IRQF_ONESHOT ,
229
+ dev_name (& pdev -> dev ),
230
+ & pdev -> dev );
231
+ if (ret ) {
232
+ dev_err (& pdev -> dev , "devm_request_threaded_irq '%d' "
233
+ "failed with %d\n" , irq , ret );
234
+ return ret ;
235
+ }
236
+ } else {
237
+ dev_err (& pdev -> dev , "Required property not found in DT node\n" );
238
+ return - ENOENT ;
148
239
}
149
240
150
241
ret = sysfs_create_file (& pdev -> dev .kobj , & dev_attr_suspend_mode .attr );
@@ -160,6 +251,9 @@ static int zynqmp_pm_remove(struct platform_device *pdev)
160
251
{
161
252
sysfs_remove_file (& pdev -> dev .kobj , & dev_attr_suspend_mode .attr );
162
253
254
+ if (!rx_chan )
255
+ mbox_free_channel (rx_chan );
256
+
163
257
return 0 ;
164
258
}
165
259
0 commit comments