blob: 283d33b70b13e455f51e05b41811993b7c72355c [file] [log] [blame]
Narendra Muppalla1b0b3352015-09-29 10:16:51 -07001/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13#include "sde_kms.h"
14#include "drm_crtc.h"
15#include "drm_crtc_helper.h"
16
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -040017#include "sde_hwio.h"
18#include "sde_hw_catalog.h"
19#include "sde_hw_intf.h"
20#include "sde_hw_mdp_ctl.h"
21#include "sde_mdp_formats.h"
22
23#include "../dsi-staging/dsi_display.h"
24
25#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
26
Narendra Muppalla1b0b3352015-09-29 10:16:51 -070027struct sde_encoder {
28 struct drm_encoder base;
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -040029 spinlock_t intf_lock;
30 bool enabled;
31 uint32_t bus_scaling_client;
32 struct sde_hw_intf *hw_intf;
33 struct sde_hw_ctl *hw_ctl;
34 int drm_mode_enc;
35
36 void (*vblank_callback)(void *);
37 void *vblank_callback_data;
38
39 struct mdp_irq vblank_irq;
Narendra Muppalla1b0b3352015-09-29 10:16:51 -070040};
41#define to_sde_encoder(x) container_of(x, struct sde_encoder, base)
42
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -040043static struct sde_kms *get_kms(struct drm_encoder *drm_enc)
Narendra Muppalla1b0b3352015-09-29 10:16:51 -070044{
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -040045 struct msm_drm_private *priv = drm_enc->dev->dev_private;
Narendra Muppalla1b0b3352015-09-29 10:16:51 -070046
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -040047 return to_sde_kms(to_mdp_kms(priv->kms));
Narendra Muppalla1b0b3352015-09-29 10:16:51 -070048}
49
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -040050#ifdef CONFIG_QCOM_BUS_SCALING
51#include <linux/msm-bus.h>
52#include <linux/msm-bus-board.h>
53#define MDP_BUS_VECTOR_ENTRY(ab_val, ib_val) \
54 { \
55 .src = MSM_BUS_MASTER_MDP_PORT0, \
56 .dst = MSM_BUS_SLAVE_EBI_CH0, \
57 .ab = (ab_val), \
58 .ib = (ib_val), \
59 }
60
61static struct msm_bus_vectors mdp_bus_vectors[] = {
62 MDP_BUS_VECTOR_ENTRY(0, 0),
63 MDP_BUS_VECTOR_ENTRY(2000000000, 2000000000),
64};
65static struct msm_bus_paths mdp_bus_usecases[] = { {
66 .num_paths = 1,
67 .vectors =
68 &mdp_bus_vectors[0],
69 }, {
70 .num_paths = 1,
71 .vectors =
72 &mdp_bus_vectors[1],
73 }
Narendra Muppalla1b0b3352015-09-29 10:16:51 -070074};
75
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -040076static struct msm_bus_scale_pdata mdp_bus_scale_table = {
77 .usecase = mdp_bus_usecases,
78 .num_usecases = ARRAY_SIZE(mdp_bus_usecases),
79 .name = "mdss_mdp",
80};
81
82static void bs_init(struct sde_encoder *sde_enc)
83{
84 sde_enc->bus_scaling_client =
85 msm_bus_scale_register_client(&mdp_bus_scale_table);
86 DBG("bus scale client: %08x", sde_enc->bus_scaling_client);
87}
88
89static void bs_fini(struct sde_encoder *sde_enc)
90{
91 if (sde_enc->bus_scaling_client) {
92 msm_bus_scale_unregister_client(sde_enc->bus_scaling_client);
93 sde_enc->bus_scaling_client = 0;
94 }
95}
96
97static void bs_set(struct sde_encoder *sde_enc, int idx)
98{
99 if (sde_enc->bus_scaling_client) {
100 DBG("set bus scaling: %d", idx);
101 idx = 1;
102 msm_bus_scale_client_update_request(sde_enc->bus_scaling_client,
103 idx);
104 }
105}
106#else
107static void bs_init(struct sde_encoder *sde_enc)
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700108{
109}
110
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400111static void bs_fini(struct sde_encoder *sde_enc)
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700112{
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400113}
114
115static void bs_set(struct sde_encoder *sde_enc, int idx)
116{
117}
118#endif
119
120static bool sde_encoder_mode_fixup(struct drm_encoder *drm_enc,
121 const struct drm_display_mode *mode,
122 struct drm_display_mode *adjusted_mode)
123{
124 DBG("");
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700125 return true;
126}
127
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400128static void sde_encoder_mode_set(struct drm_encoder *drm_enc,
129 struct drm_display_mode *mode,
130 struct drm_display_mode *adjusted_mode)
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700131{
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400132
133 struct sde_encoder *sde_enc = to_sde_encoder(drm_enc);
134 struct intf_timing_params p = {0};
135 uint32_t hsync_polarity = 0, vsync_polarity = 0;
136 struct sde_mdp_format_params *sde_fmt_params = NULL;
137 u32 fmt_fourcc = DRM_FORMAT_RGB888, fmt_mod = 0;
138 unsigned long lock_flags;
139 struct sde_hw_intf_cfg intf_cfg = {0};
140
141 mode = adjusted_mode;
142
143 DBG("set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
144 mode->base.id, mode->name, mode->vrefresh, mode->clock,
145 mode->hdisplay, mode->hsync_start, mode->hsync_end, mode->htotal,
146 mode->vdisplay, mode->vsync_start, mode->vsync_end, mode->vtotal,
147 mode->type, mode->flags);
148
149 /* DSI controller cannot handle active-low sync signals. */
150 if (sde_enc->hw_intf->cap->type != INTF_DSI) {
151 if (mode->flags & DRM_MODE_FLAG_NHSYNC)
152 hsync_polarity = 1;
153 if (mode->flags & DRM_MODE_FLAG_NVSYNC)
154 vsync_polarity = 1;
155 }
156
157 /*
158 * For edp only:
159 * DISPLAY_V_START = (VBP * HCYCLE) + HBP
160 * DISPLAY_V_END = (VBP + VACTIVE) * HCYCLE - 1 - HFP
161 */
162 /*
163 * if (sde_enc->hw->cap->type == INTF_EDP) {
164 * display_v_start += mode->htotal - mode->hsync_start;
165 * display_v_end -= mode->hsync_start - mode->hdisplay;
166 * }
167 */
168
169 /*
170 * https://www.kernel.org/doc/htmldocs/drm/ch02s05.html
171 * Active Region Front Porch Sync Back Porch
172 * <---------------------><----------------><---------><-------------->
173 * <--- [hv]display ----->
174 * <----------- [hv]sync_start ------------>
175 * <------------------- [hv]sync_end ----------------->
176 * <------------------------------ [hv]total ------------------------->
177 */
178
179 sde_fmt_params = sde_mdp_get_format_params(fmt_fourcc, fmt_mod);
180
181 p.width = mode->hdisplay; /* active width */
182 p.height = mode->vdisplay; /* active height */
183 p.xres = p.width; /* Display panel width */
184 p.yres = p.height; /* Display panel height */
185 p.h_back_porch = mode->htotal - mode->hsync_end;
186 p.h_front_porch = mode->hsync_start - mode->hdisplay;
187 p.v_back_porch = mode->vtotal - mode->vsync_end;
188 p.v_front_porch = mode->vsync_start - mode->vdisplay;
189 p.hsync_pulse_width = mode->hsync_end - mode->hsync_start;
190 p.vsync_pulse_width = mode->vsync_end - mode->vsync_start;
191 p.hsync_polarity = hsync_polarity;
192 p.vsync_polarity = vsync_polarity;
193 p.border_clr = 0;
194 p.underflow_clr = 0xff;
195 p.hsync_skew = mode->hskew;
196
197 intf_cfg.intf = sde_enc->hw_intf->idx;
198 intf_cfg.wb = SDE_NONE;
199
200 spin_lock_irqsave(&sde_enc->intf_lock, lock_flags);
201 sde_enc->hw_intf->ops.setup_timing_gen(sde_enc->hw_intf, &p,
202 sde_fmt_params);
203 sde_enc->hw_ctl->ops.setup_intf_cfg(sde_enc->hw_ctl, &intf_cfg);
204 spin_unlock_irqrestore(&sde_enc->intf_lock, lock_flags);
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700205}
206
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400207static void sde_encoder_wait_for_vblank(struct sde_encoder *sde_enc)
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700208{
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400209 struct sde_kms *sde_kms = get_kms(&sde_enc->base);
210 struct mdp_kms *mdp_kms = &sde_kms->base;
211
212 DBG("");
213 mdp_irq_wait(mdp_kms, sde_enc->vblank_irq.irqmask);
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700214}
215
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400216static void sde_encoder_vblank_irq(struct mdp_irq *irq, uint32_t irqstatus)
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700217{
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400218 struct sde_encoder *sde_enc = container_of(irq, struct sde_encoder,
219 vblank_irq);
220 struct intf_status status = { 0 };
221 unsigned long lock_flags;
222
223 spin_lock_irqsave(&sde_enc->intf_lock, lock_flags);
224 if (sde_enc->vblank_callback)
225 sde_enc->vblank_callback(sde_enc->vblank_callback_data);
226 spin_unlock_irqrestore(&sde_enc->intf_lock, lock_flags);
227
228 sde_enc->hw_intf->ops.get_status(sde_enc->hw_intf, &status);
229}
230
231static void sde_encoder_disable(struct drm_encoder *drm_enc)
232{
233 struct sde_encoder *sde_enc = to_sde_encoder(drm_enc);
234 struct sde_kms *sde_kms = get_kms(drm_enc);
235 struct mdp_kms *mdp_kms = &(sde_kms->base);
236 unsigned long lock_flags;
237
238 DBG("");
239
240 if (WARN_ON(!sde_enc->enabled))
241 return;
242
243 spin_lock_irqsave(&sde_enc->intf_lock, lock_flags);
244 sde_enc->hw_intf->ops.enable_timing(sde_enc->hw_intf, 0);
245 spin_unlock_irqrestore(&sde_enc->intf_lock, lock_flags);
246
247 /*
248 * Wait for a vsync so we know the ENABLE=0 latched before
249 * the (connector) source of the vsync's gets disabled,
250 * otherwise we end up in a funny state if we re-enable
251 * before the disable latches, which results that some of
252 * the settings changes for the new modeset (like new
253 * scanout buffer) don't latch properly..
254 */
255 sde_encoder_wait_for_vblank(sde_enc);
256
257 mdp_irq_unregister(mdp_kms, &sde_enc->vblank_irq);
258 bs_set(sde_enc, 0);
259 sde_enc->enabled = false;
260}
261
262static void sde_encoder_enable(struct drm_encoder *drm_enc)
263{
264 struct sde_encoder *sde_enc = to_sde_encoder(drm_enc);
265 struct mdp_kms *mdp_kms = &(get_kms(drm_enc)->base);
266 unsigned long lock_flags;
267
268 DBG("");
269
270 if (WARN_ON(sde_enc->enabled))
271 return;
272
273 bs_set(sde_enc, 1);
274 spin_lock_irqsave(&sde_enc->intf_lock, lock_flags);
275 sde_enc->hw_intf->ops.enable_timing(sde_enc->hw_intf, 1);
276 spin_unlock_irqrestore(&sde_enc->intf_lock, lock_flags);
277 sde_enc->enabled = true;
278
279 mdp_irq_register(mdp_kms, &sde_enc->vblank_irq);
280 DBG("Registered IRQ for intf %d mask 0x%X", sde_enc->hw_intf->idx,
281 sde_enc->vblank_irq.irqmask);
282}
283
284void sde_encoder_get_hw_resources(struct drm_encoder *drm_enc,
285 struct sde_encoder_hw_resources *hw_res)
286{
287 struct sde_encoder *sde_enc = to_sde_encoder(drm_enc);
288
289 DBG("");
290
291 if (WARN_ON(!hw_res))
292 return;
293
294 memset(hw_res, 0, sizeof(*hw_res));
295 hw_res->intfs[sde_enc->hw_intf->idx] = true;
296}
297
298static void sde_encoder_destroy(struct drm_encoder *drm_enc)
299{
300 struct sde_encoder *sde_enc = to_sde_encoder(drm_enc);
301
302 DBG("");
303 drm_encoder_cleanup(drm_enc);
304 bs_fini(sde_enc);
305 kfree(sde_enc->hw_intf);
306 kfree(sde_enc);
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700307}
308
309static const struct drm_encoder_helper_funcs sde_encoder_helper_funcs = {
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700310 .mode_fixup = sde_encoder_mode_fixup,
311 .mode_set = sde_encoder_mode_set,
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400312 .disable = sde_encoder_disable,
313 .enable = sde_encoder_enable,
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700314};
315
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400316static const struct drm_encoder_funcs sde_encoder_funcs = {.destroy =
317 sde_encoder_destroy,
318};
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700319
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400320static int sde_encoder_setup_hw(struct sde_encoder *sde_enc,
321 struct sde_kms *sde_kms,
322 enum sde_intf intf_idx,
323 enum sde_ctl ctl_idx)
324{
325 int ret = 0;
326
327 DBG("");
328
329 sde_enc->hw_intf = sde_hw_intf_init(intf_idx, sde_kms->mmio,
330 sde_kms->catalog);
331 if (!sde_enc->hw_intf)
332 return -EINVAL;
333
334 sde_enc->hw_ctl = sde_hw_ctl_init(ctl_idx, sde_kms->mmio,
335 sde_kms->catalog);
336 if (!sde_enc->hw_ctl)
337 return -EINVAL;
338
339 return ret;
340}
341
342static int sde_encoder_virt_add_phys_vid_enc(struct sde_encoder *sde_enc,
343 struct sde_kms *sde_kms,
344 enum sde_intf intf_idx,
345 enum sde_ctl ctl_idx)
346{
347 int ret = 0;
348
349 DBG("");
350
351 ret = sde_encoder_setup_hw(sde_enc, sde_kms, intf_idx, ctl_idx);
352 if (!ret) {
353 sde_enc->vblank_irq.irq = sde_encoder_vblank_irq;
354 sde_enc->vblank_irq.irqmask = 0x8000000;
355 }
356 return ret;
357}
358
359static int sde_encoder_setup_hdmi(struct sde_encoder *sde_enc,
360 struct sde_kms *sde_kms, int *hdmi_info)
361{
362 int ret = 0;
363 enum sde_intf intf_idx = INTF_MAX;
364
365 DBG("");
366
367 sde_enc->drm_mode_enc = DRM_MODE_ENCODER_TMDS;
368
369 intf_idx = INTF_3;
370 if (intf_idx == INTF_MAX)
371 ret = -EINVAL;
372
373 if (!ret)
374 ret =
375 sde_encoder_virt_add_phys_vid_enc(sde_enc, sde_kms,
376 intf_idx,
377 CTL_2);
378
379 return ret;
380}
381
382static int sde_encoder_setup_dsi(struct sde_encoder *sde_enc,
383 struct sde_kms *sde_kms,
384 struct dsi_display_info *dsi_info)
385{
386 int ret = 0;
387 int i = 0;
388
389 DBG("");
390
391 sde_enc->drm_mode_enc = DRM_MODE_ENCODER_DSI;
392
393 if (WARN_ON(dsi_info->num_of_h_tiles > 1)) {
394 DBG("Dual DSI mode not yet supported");
395 ret = -EINVAL;
396 }
397
398 WARN_ON(dsi_info->num_of_h_tiles != 1);
399 dsi_info->num_of_h_tiles = 1;
400
401 DBG("dsi_info->num_of_h_tiles %d h_tiled %d dsi_info->h_tile_ids %d ",
402 dsi_info->num_of_h_tiles, dsi_info->h_tiled,
403 dsi_info->h_tile_ids[0]);
404
405 for (i = 0; i < !ret && dsi_info->num_of_h_tiles; i++) {
406 enum sde_intf intf_idx = INTF_1;
407 enum sde_ctl ctl_idx = CTL_0;
408
409 if (intf_idx == INTF_MAX) {
410 DBG("Error: could not get the interface id");
411 ret = -EINVAL;
412 }
413
414 /* Get DSI modes, create both VID & CMD Phys Encoders */
415 if (!ret)
416 ret =
417 sde_encoder_virt_add_phys_vid_enc(sde_enc, sde_kms,
418 intf_idx,
419 ctl_idx);
420 }
421
422 return ret;
423}
424
425struct display_probe_info {
426 enum sde_intf_type type;
427 struct dsi_display_info dsi_info;
428 int hdmi_info;
429};
430
431static struct drm_encoder *sde_encoder_virt_init(struct drm_device *dev,
432 struct display_probe_info
433 *display)
434{
435 struct msm_drm_private *priv = dev->dev_private;
436 struct sde_kms *sde_kms = to_sde_kms(to_mdp_kms(priv->kms));
437 struct drm_encoder *drm_enc = NULL;
438 struct sde_encoder *sde_enc = NULL;
439 int ret = 0;
440
441 DBG("");
442
443 sde_enc = kzalloc(sizeof(*sde_enc), GFP_KERNEL);
444 if (!sde_enc) {
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700445 ret = -ENOMEM;
446 goto fail;
447 }
448
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400449 if (display->type == INTF_DSI) {
450 ret =
451 sde_encoder_setup_dsi(sde_enc, sde_kms, &display->dsi_info);
452 } else if (display->type == INTF_HDMI) {
453 ret =
454 sde_encoder_setup_hdmi(sde_enc, sde_kms,
455 &display->hdmi_info);
456 } else {
457 DBG("No valid displays found");
458 ret = -EINVAL;
459 }
460 if (ret)
461 goto fail;
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700462
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400463 spin_lock_init(&sde_enc->intf_lock);
464 drm_enc = &sde_enc->base;
465 drm_encoder_init(dev, drm_enc, &sde_encoder_funcs,
466 sde_enc->drm_mode_enc);
467 drm_encoder_helper_add(drm_enc, &sde_encoder_helper_funcs);
468 bs_init(sde_enc);
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700469
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400470 DBG("Created sde_encoder for intf %d", sde_enc->hw_intf->idx);
471
472 return drm_enc;
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700473
474fail:
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400475 if (drm_enc)
476 sde_encoder_destroy(drm_enc);
Narendra Muppalla1b0b3352015-09-29 10:16:51 -0700477
478 return ERR_PTR(ret);
479}
Abhijit Kulkarni3e3e0d22016-06-24 17:56:13 -0400480
481static int sde_encoder_probe_hdmi(struct drm_device *dev)
482{
483 struct msm_drm_private *priv = dev->dev_private;
484 struct drm_encoder *enc = NULL;
485 struct display_probe_info probe_info = { 0 };
486 int ret = 0;
487
488 DBG("");
489
490 probe_info.type = INTF_HDMI;
491
492 enc = sde_encoder_virt_init(dev, &probe_info);
493 if (IS_ERR(enc))
494 ret = PTR_ERR(enc);
495 else {
496 /* Register new encoder with the upper layer */
497 priv->encoders[priv->num_encoders++] = enc;
498 }
499 return ret;
500}
501
502static int sde_encoder_probe_dsi(struct drm_device *dev)
503{
504 struct msm_drm_private *priv = dev->dev_private;
505 u32 ret = 0;
506 u32 i = 0;
507 u32 num_displays = 0;
508
509 DBG("");
510
511 num_displays = dsi_display_get_num_of_displays();
512 DBG("num_displays %d", num_displays);
513 for (i = 0; i < num_displays; i++) {
514 struct dsi_display *dsi = dsi_display_get_display_by_index(i);
515
516 if (dsi_display_is_active(dsi)) {
517 struct display_probe_info probe_info = { 0 };
518
519 DBG("display %d/%d is active", i, num_displays);
520 probe_info.type = INTF_DSI;
521
522 ret = dsi_display_get_info(dsi, &probe_info.dsi_info);
523 if (WARN_ON(ret))
524 DBG("Failed to retrieve dsi panel info");
525 else {
526 struct drm_encoder *enc =
527 sde_encoder_virt_init(dev,
528 &probe_info);
529 if (IS_ERR(enc))
530 return PTR_ERR(enc);
531
532 ret = dsi_display_drm_init(dsi, enc);
533 if (ret)
534 return ret;
535
536 /* Register new encoder with the upper layer */
537 priv->encoders[priv->num_encoders++] = enc;
538 }
539 } else
540 DBG("display %d/%d is not active", i, num_displays);
541 }
542
543 return ret;
544}
545
546void sde_encoder_register_vblank_callback(struct drm_encoder *drm_enc,
547 void (*cb)(void *), void *data) {
548 struct sde_encoder *sde_enc = to_sde_encoder(drm_enc);
549 unsigned long lock_flags;
550
551 DBG("");
552
553 spin_lock_irqsave(&sde_enc->intf_lock, lock_flags);
554 sde_enc->vblank_callback = cb;
555 sde_enc->vblank_callback_data = data;
556 spin_unlock_irqrestore(&sde_enc->intf_lock, lock_flags);
557}
558
559/* encoders init,
560 * initialize encoder based on displays
561 */
562void sde_encoders_init(struct drm_device *dev)
563{
564 struct msm_drm_private *priv = dev->dev_private;
565 int ret = 0;
566
567 DBG("");
568
569 /* Start num_encoders at 0, probe functions will increment */
570 priv->num_encoders = 0;
571 ret = sde_encoder_probe_dsi(dev);
572 if (ret)
573 DBG("Error probing DSI, %d", ret);
574 else {
575 ret = sde_encoder_probe_hdmi(dev);
576 if (ret)
577 DBG("Error probing HDMI, %d", ret);
578 }
579}