diff options
Diffstat (limited to 'drivers/gpu/drm/shmobile/shmob_drm_crtc.c')
| -rw-r--r-- | drivers/gpu/drm/shmobile/shmob_drm_crtc.c | 685 |
1 files changed, 0 insertions, 685 deletions
diff --git a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c deleted file mode 100644 index d354ab3077ce..000000000000 --- a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c +++ /dev/null @@ -1,685 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * shmob_drm_crtc.c -- SH Mobile DRM CRTCs - * - * Copyright (C) 2012 Renesas Electronics Corporation - * - * Laurent Pinchart (laurent.pinchart@ideasonboard.com) - */ - -#include <linux/backlight.h> -#include <linux/clk.h> - -#include <drm/drm_crtc.h> -#include <drm/drm_crtc_helper.h> -#include <drm/drm_fb_dma_helper.h> -#include <drm/drm_fourcc.h> -#include <drm/drm_framebuffer.h> -#include <drm/drm_gem_dma_helper.h> -#include <drm/drm_modeset_helper.h> -#include <drm/drm_modeset_helper_vtables.h> -#include <drm/drm_probe_helper.h> -#include <drm/drm_simple_kms_helper.h> -#include <drm/drm_vblank.h> - -#include "shmob_drm_backlight.h" -#include "shmob_drm_crtc.h" -#include "shmob_drm_drv.h" -#include "shmob_drm_kms.h" -#include "shmob_drm_plane.h" -#include "shmob_drm_regs.h" - -/* - * TODO: panel support - */ - -/* ----------------------------------------------------------------------------- - * Clock management - */ - -static int shmob_drm_clk_on(struct shmob_drm_device *sdev) -{ - int ret; - - if (sdev->clock) { - ret = clk_prepare_enable(sdev->clock); - if (ret < 0) - return ret; - } - - return 0; -} - -static void shmob_drm_clk_off(struct shmob_drm_device *sdev) -{ - if (sdev->clock) - clk_disable_unprepare(sdev->clock); -} - -/* ----------------------------------------------------------------------------- - * CRTC - */ - -static void shmob_drm_crtc_setup_geometry(struct shmob_drm_crtc *scrtc) -{ - struct drm_crtc *crtc = &scrtc->crtc; - struct shmob_drm_device *sdev = crtc->dev->dev_private; - const struct shmob_drm_interface_data *idata = &sdev->pdata->iface; - const struct drm_display_mode *mode = &crtc->mode; - u32 value; - - value = sdev->ldmt1r - | ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : LDMT1R_VPOL) - | ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : LDMT1R_HPOL) - | ((idata->flags & SHMOB_DRM_IFACE_FL_DWPOL) ? LDMT1R_DWPOL : 0) - | ((idata->flags & SHMOB_DRM_IFACE_FL_DIPOL) ? LDMT1R_DIPOL : 0) - | ((idata->flags & SHMOB_DRM_IFACE_FL_DAPOL) ? LDMT1R_DAPOL : 0) - | ((idata->flags & SHMOB_DRM_IFACE_FL_HSCNT) ? LDMT1R_HSCNT : 0) - | ((idata->flags & SHMOB_DRM_IFACE_FL_DWCNT) ? LDMT1R_DWCNT : 0); - lcdc_write(sdev, LDMT1R, value); - - if (idata->interface >= SHMOB_DRM_IFACE_SYS8A && - idata->interface <= SHMOB_DRM_IFACE_SYS24) { - /* Setup SYS bus. */ - value = (idata->sys.cs_setup << LDMT2R_CSUP_SHIFT) - | (idata->sys.vsync_active_high ? LDMT2R_RSV : 0) - | (idata->sys.vsync_dir_input ? LDMT2R_VSEL : 0) - | (idata->sys.write_setup << LDMT2R_WCSC_SHIFT) - | (idata->sys.write_cycle << LDMT2R_WCEC_SHIFT) - | (idata->sys.write_strobe << LDMT2R_WCLW_SHIFT); - lcdc_write(sdev, LDMT2R, value); - - value = (idata->sys.read_latch << LDMT3R_RDLC_SHIFT) - | (idata->sys.read_setup << LDMT3R_RCSC_SHIFT) - | (idata->sys.read_cycle << LDMT3R_RCEC_SHIFT) - | (idata->sys.read_strobe << LDMT3R_RCLW_SHIFT); - lcdc_write(sdev, LDMT3R, value); - } - - value = ((mode->hdisplay / 8) << 16) /* HDCN */ - | (mode->htotal / 8); /* HTCN */ - lcdc_write(sdev, LDHCNR, value); - - value = (((mode->hsync_end - mode->hsync_start) / 8) << 16) /* HSYNW */ - | (mode->hsync_start / 8); /* HSYNP */ - lcdc_write(sdev, LDHSYNR, value); - - value = ((mode->hdisplay & 7) << 24) | ((mode->htotal & 7) << 16) - | (((mode->hsync_end - mode->hsync_start) & 7) << 8) - | (mode->hsync_start & 7); - lcdc_write(sdev, LDHAJR, value); - - value = ((mode->vdisplay) << 16) /* VDLN */ - | mode->vtotal; /* VTLN */ - lcdc_write(sdev, LDVLNR, value); - - value = ((mode->vsync_end - mode->vsync_start) << 16) /* VSYNW */ - | mode->vsync_start; /* VSYNP */ - lcdc_write(sdev, LDVSYNR, value); -} - -static void shmob_drm_crtc_start_stop(struct shmob_drm_crtc *scrtc, bool start) -{ - struct shmob_drm_device *sdev = scrtc->crtc.dev->dev_private; - u32 value; - - value = lcdc_read(sdev, LDCNT2R); - if (start) - lcdc_write(sdev, LDCNT2R, value | LDCNT2R_DO); - else - lcdc_write(sdev, LDCNT2R, value & ~LDCNT2R_DO); - - /* Wait until power is applied/stopped. */ - while (1) { - value = lcdc_read(sdev, LDPMR) & LDPMR_LPS; - if ((start && value) || (!start && !value)) - break; - - cpu_relax(); - } - - if (!start) { - /* Stop the dot clock. */ - lcdc_write(sdev, LDDCKSTPR, LDDCKSTPR_DCKSTP); - } -} - -/* - * shmob_drm_crtc_start - Configure and start the LCDC - * @scrtc: the SH Mobile CRTC - * - * Configure and start the LCDC device. External devices (clocks, MERAM, panels, - * ...) are not touched by this function. - */ -static void shmob_drm_crtc_start(struct shmob_drm_crtc *scrtc) -{ - struct drm_crtc *crtc = &scrtc->crtc; - struct shmob_drm_device *sdev = crtc->dev->dev_private; - const struct shmob_drm_interface_data *idata = &sdev->pdata->iface; - const struct shmob_drm_format_info *format; - struct drm_device *dev = sdev->ddev; - struct drm_plane *plane; - u32 value; - int ret; - - if (scrtc->started) - return; - - format = shmob_drm_format_info(crtc->primary->fb->format->format); - if (WARN_ON(format == NULL)) - return; - - /* Enable clocks before accessing the hardware. */ - ret = shmob_drm_clk_on(sdev); - if (ret < 0) - return; - - /* Reset and enable the LCDC. */ - lcdc_write(sdev, LDCNT2R, lcdc_read(sdev, LDCNT2R) | LDCNT2R_BR); - lcdc_wait_bit(sdev, LDCNT2R, LDCNT2R_BR, 0); - lcdc_write(sdev, LDCNT2R, LDCNT2R_ME); - - /* Stop the LCDC first and disable all interrupts. */ - shmob_drm_crtc_start_stop(scrtc, false); - lcdc_write(sdev, LDINTR, 0); - - /* Configure power supply, dot clocks and start them. */ - lcdc_write(sdev, LDPMR, 0); - - value = sdev->lddckr; - if (idata->clk_div) { - /* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider - * denominator. - */ - lcdc_write(sdev, LDDCKPAT1R, 0); - lcdc_write(sdev, LDDCKPAT2R, (1 << (idata->clk_div / 2)) - 1); - - if (idata->clk_div == 1) - value |= LDDCKR_MOSEL; - else - value |= idata->clk_div; - } - - lcdc_write(sdev, LDDCKR, value); - lcdc_write(sdev, LDDCKSTPR, 0); - lcdc_wait_bit(sdev, LDDCKSTPR, ~0, 0); - - /* TODO: Setup SYS panel */ - - /* Setup geometry, format, frame buffer memory and operation mode. */ - shmob_drm_crtc_setup_geometry(scrtc); - - /* TODO: Handle YUV colorspaces. Hardcode REC709 for now. */ - lcdc_write(sdev, LDDFR, format->lddfr | LDDFR_CF1); - lcdc_write(sdev, LDMLSR, scrtc->line_size); - lcdc_write(sdev, LDSA1R, scrtc->dma[0]); - if (format->yuv) - lcdc_write(sdev, LDSA2R, scrtc->dma[1]); - lcdc_write(sdev, LDSM1R, 0); - - /* Word and long word swap. */ - switch (format->fourcc) { - case DRM_FORMAT_RGB565: - case DRM_FORMAT_NV21: - case DRM_FORMAT_NV61: - case DRM_FORMAT_NV42: - value = LDDDSR_LS | LDDDSR_WS; - break; - case DRM_FORMAT_RGB888: - case DRM_FORMAT_NV12: - case DRM_FORMAT_NV16: - case DRM_FORMAT_NV24: - value = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS; - break; - case DRM_FORMAT_ARGB8888: - default: - value = LDDDSR_LS; - break; - } - lcdc_write(sdev, LDDDSR, value); - - /* Setup planes. */ - drm_for_each_legacy_plane(plane, dev) { - if (plane->crtc == crtc) - shmob_drm_plane_setup(plane); - } - - /* Enable the display output. */ - lcdc_write(sdev, LDCNT1R, LDCNT1R_DE); - - shmob_drm_crtc_start_stop(scrtc, true); - - scrtc->started = true; -} - -static void shmob_drm_crtc_stop(struct shmob_drm_crtc *scrtc) -{ - struct drm_crtc *crtc = &scrtc->crtc; - struct shmob_drm_device *sdev = crtc->dev->dev_private; - - if (!scrtc->started) - return; - - /* Stop the LCDC. */ - shmob_drm_crtc_start_stop(scrtc, false); - - /* Disable the display output. */ - lcdc_write(sdev, LDCNT1R, 0); - - /* Stop clocks. */ - shmob_drm_clk_off(sdev); - - scrtc->started = false; -} - -void shmob_drm_crtc_suspend(struct shmob_drm_crtc *scrtc) -{ - shmob_drm_crtc_stop(scrtc); -} - -void shmob_drm_crtc_resume(struct shmob_drm_crtc *scrtc) -{ - if (scrtc->dpms != DRM_MODE_DPMS_ON) - return; - - shmob_drm_crtc_start(scrtc); -} - -static void shmob_drm_crtc_compute_base(struct shmob_drm_crtc *scrtc, - int x, int y) -{ - struct drm_crtc *crtc = &scrtc->crtc; - struct drm_framebuffer *fb = crtc->primary->fb; - struct drm_gem_dma_object *gem; - unsigned int bpp; - - bpp = scrtc->format->yuv ? 8 : scrtc->format->bpp; - gem = drm_fb_dma_get_gem_obj(fb, 0); - scrtc->dma[0] = gem->dma_addr + fb->offsets[0] - + y * fb->pitches[0] + x * bpp / 8; - - if (scrtc->format->yuv) { - bpp = scrtc->format->bpp - 8; - gem = drm_fb_dma_get_gem_obj(fb, 1); - scrtc->dma[1] = gem->dma_addr + fb->offsets[1] - + y / (bpp == 4 ? 2 : 1) * fb->pitches[1] - + x * (bpp == 16 ? 2 : 1); - } -} - -static void shmob_drm_crtc_update_base(struct shmob_drm_crtc *scrtc) -{ - struct drm_crtc *crtc = &scrtc->crtc; - struct shmob_drm_device *sdev = crtc->dev->dev_private; - - shmob_drm_crtc_compute_base(scrtc, crtc->x, crtc->y); - - lcdc_write_mirror(sdev, LDSA1R, scrtc->dma[0]); - if (scrtc->format->yuv) - lcdc_write_mirror(sdev, LDSA2R, scrtc->dma[1]); - - lcdc_write(sdev, LDRCNTR, lcdc_read(sdev, LDRCNTR) ^ LDRCNTR_MRS); -} - -#define to_shmob_crtc(c) container_of(c, struct shmob_drm_crtc, crtc) - -static void shmob_drm_crtc_dpms(struct drm_crtc *crtc, int mode) -{ - struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc); - - if (scrtc->dpms == mode) - return; - - if (mode == DRM_MODE_DPMS_ON) - shmob_drm_crtc_start(scrtc); - else - shmob_drm_crtc_stop(scrtc); - - scrtc->dpms = mode; -} - -static void shmob_drm_crtc_mode_prepare(struct drm_crtc *crtc) -{ - shmob_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); -} - -static int shmob_drm_crtc_mode_set(struct drm_crtc *crtc, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode, - int x, int y, - struct drm_framebuffer *old_fb) -{ - struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc); - struct shmob_drm_device *sdev = crtc->dev->dev_private; - const struct shmob_drm_format_info *format; - - format = shmob_drm_format_info(crtc->primary->fb->format->format); - if (format == NULL) { - dev_dbg(sdev->dev, "mode_set: unsupported format %08x\n", - crtc->primary->fb->format->format); - return -EINVAL; - } - - scrtc->format = format; - scrtc->line_size = crtc->primary->fb->pitches[0]; - - shmob_drm_crtc_compute_base(scrtc, x, y); - - return 0; -} - -static void shmob_drm_crtc_mode_commit(struct drm_crtc *crtc) -{ - shmob_drm_crtc_dpms(crtc, DRM_MODE_DPMS_ON); -} - -static int shmob_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, - struct drm_framebuffer *old_fb) -{ - shmob_drm_crtc_update_base(to_shmob_crtc(crtc)); - - return 0; -} - -static const struct drm_crtc_helper_funcs crtc_helper_funcs = { - .dpms = shmob_drm_crtc_dpms, - .prepare = shmob_drm_crtc_mode_prepare, - .commit = shmob_drm_crtc_mode_commit, - .mode_set = shmob_drm_crtc_mode_set, - .mode_set_base = shmob_drm_crtc_mode_set_base, -}; - -void shmob_drm_crtc_finish_page_flip(struct shmob_drm_crtc *scrtc) -{ - struct drm_pending_vblank_event *event; - struct drm_device *dev = scrtc->crtc.dev; - unsigned long flags; - - spin_lock_irqsave(&dev->event_lock, flags); - event = scrtc->event; - scrtc->event = NULL; - if (event) { - drm_crtc_send_vblank_event(&scrtc->crtc, event); - drm_crtc_vblank_put(&scrtc->crtc); - } - spin_unlock_irqrestore(&dev->event_lock, flags); -} - -static int shmob_drm_crtc_page_flip(struct drm_crtc *crtc, - struct drm_framebuffer *fb, - struct drm_pending_vblank_event *event, - uint32_t page_flip_flags, - struct drm_modeset_acquire_ctx *ctx) -{ - struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc); - struct drm_device *dev = scrtc->crtc.dev; - unsigned long flags; - - spin_lock_irqsave(&dev->event_lock, flags); - if (scrtc->event != NULL) { - spin_unlock_irqrestore(&dev->event_lock, flags); - return -EBUSY; - } - spin_unlock_irqrestore(&dev->event_lock, flags); - - crtc->primary->fb = fb; - shmob_drm_crtc_update_base(scrtc); - - if (event) { - event->pipe = 0; - drm_crtc_vblank_get(&scrtc->crtc); - spin_lock_irqsave(&dev->event_lock, flags); - scrtc->event = event; - spin_unlock_irqrestore(&dev->event_lock, flags); - } - - return 0; -} - -static void shmob_drm_crtc_enable_vblank(struct shmob_drm_device *sdev, - bool enable) -{ - unsigned long flags; - u32 ldintr; - - /* Be careful not to acknowledge any pending interrupt. */ - spin_lock_irqsave(&sdev->irq_lock, flags); - ldintr = lcdc_read(sdev, LDINTR) | LDINTR_STATUS_MASK; - if (enable) - ldintr |= LDINTR_VEE; - else - ldintr &= ~LDINTR_VEE; - lcdc_write(sdev, LDINTR, ldintr); - spin_unlock_irqrestore(&sdev->irq_lock, flags); -} - -static int shmob_drm_enable_vblank(struct drm_crtc *crtc) -{ - struct shmob_drm_device *sdev = crtc->dev->dev_private; - - shmob_drm_crtc_enable_vblank(sdev, true); - - return 0; -} - -static void shmob_drm_disable_vblank(struct drm_crtc *crtc) -{ - struct shmob_drm_device *sdev = crtc->dev->dev_private; - - shmob_drm_crtc_enable_vblank(sdev, false); -} - -static const struct drm_crtc_funcs crtc_funcs = { - .destroy = drm_crtc_cleanup, - .set_config = drm_crtc_helper_set_config, - .page_flip = shmob_drm_crtc_page_flip, - .enable_vblank = shmob_drm_enable_vblank, - .disable_vblank = shmob_drm_disable_vblank, -}; - -int shmob_drm_crtc_create(struct shmob_drm_device *sdev) -{ - struct drm_crtc *crtc = &sdev->crtc.crtc; - int ret; - - sdev->crtc.dpms = DRM_MODE_DPMS_OFF; - - ret = drm_crtc_init(sdev->ddev, crtc, &crtc_funcs); - if (ret < 0) - return ret; - - drm_crtc_helper_add(crtc, &crtc_helper_funcs); - - return 0; -} - -/* ----------------------------------------------------------------------------- - * Encoder - */ - -#define to_shmob_encoder(e) \ - container_of(e, struct shmob_drm_encoder, encoder) - -static void shmob_drm_encoder_dpms(struct drm_encoder *encoder, int mode) -{ - struct shmob_drm_encoder *senc = to_shmob_encoder(encoder); - struct shmob_drm_device *sdev = encoder->dev->dev_private; - struct shmob_drm_connector *scon = &sdev->connector; - - if (senc->dpms == mode) - return; - - shmob_drm_backlight_dpms(scon, mode); - - senc->dpms = mode; -} - -static bool shmob_drm_encoder_mode_fixup(struct drm_encoder *encoder, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) -{ - struct drm_device *dev = encoder->dev; - struct shmob_drm_device *sdev = dev->dev_private; - struct drm_connector *connector = &sdev->connector.connector; - const struct drm_display_mode *panel_mode; - - if (list_empty(&connector->modes)) { - dev_dbg(dev->dev, "mode_fixup: empty modes list\n"); - return false; - } - - /* The flat panel mode is fixed, just copy it to the adjusted mode. */ - panel_mode = list_first_entry(&connector->modes, - struct drm_display_mode, head); - drm_mode_copy(adjusted_mode, panel_mode); - - return true; -} - -static void shmob_drm_encoder_mode_prepare(struct drm_encoder *encoder) -{ - /* No-op, everything is handled in the CRTC code. */ -} - -static void shmob_drm_encoder_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) -{ - /* No-op, everything is handled in the CRTC code. */ -} - -static void shmob_drm_encoder_mode_commit(struct drm_encoder *encoder) -{ - /* No-op, everything is handled in the CRTC code. */ -} - -static const struct drm_encoder_helper_funcs encoder_helper_funcs = { - .dpms = shmob_drm_encoder_dpms, - .mode_fixup = shmob_drm_encoder_mode_fixup, - .prepare = shmob_drm_encoder_mode_prepare, - .commit = shmob_drm_encoder_mode_commit, - .mode_set = shmob_drm_encoder_mode_set, -}; - -int shmob_drm_encoder_create(struct shmob_drm_device *sdev) -{ - struct drm_encoder *encoder = &sdev->encoder.encoder; - int ret; - - sdev->encoder.dpms = DRM_MODE_DPMS_OFF; - - encoder->possible_crtcs = 1; - - ret = drm_simple_encoder_init(sdev->ddev, encoder, - DRM_MODE_ENCODER_LVDS); - if (ret < 0) - return ret; - - drm_encoder_helper_add(encoder, &encoder_helper_funcs); - - return 0; -} - -/* ----------------------------------------------------------------------------- - * Connector - */ - -#define to_shmob_connector(c) \ - container_of(c, struct shmob_drm_connector, connector) - -static int shmob_drm_connector_get_modes(struct drm_connector *connector) -{ - struct shmob_drm_device *sdev = connector->dev->dev_private; - struct drm_display_mode *mode; - - mode = drm_mode_create(connector->dev); - if (mode == NULL) - return 0; - - mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; - mode->clock = sdev->pdata->panel.mode.clock; - mode->hdisplay = sdev->pdata->panel.mode.hdisplay; - mode->hsync_start = sdev->pdata->panel.mode.hsync_start; - mode->hsync_end = sdev->pdata->panel.mode.hsync_end; - mode->htotal = sdev->pdata->panel.mode.htotal; - mode->vdisplay = sdev->pdata->panel.mode.vdisplay; - mode->vsync_start = sdev->pdata->panel.mode.vsync_start; - mode->vsync_end = sdev->pdata->panel.mode.vsync_end; - mode->vtotal = sdev->pdata->panel.mode.vtotal; - mode->flags = sdev->pdata->panel.mode.flags; - - drm_mode_set_name(mode); - drm_mode_probed_add(connector, mode); - - connector->display_info.width_mm = sdev->pdata->panel.width_mm; - connector->display_info.height_mm = sdev->pdata->panel.height_mm; - - return 1; -} - -static struct drm_encoder * -shmob_drm_connector_best_encoder(struct drm_connector *connector) -{ - struct shmob_drm_connector *scon = to_shmob_connector(connector); - - return scon->encoder; -} - -static const struct drm_connector_helper_funcs connector_helper_funcs = { - .get_modes = shmob_drm_connector_get_modes, - .best_encoder = shmob_drm_connector_best_encoder, -}; - -static void shmob_drm_connector_destroy(struct drm_connector *connector) -{ - struct shmob_drm_connector *scon = to_shmob_connector(connector); - - shmob_drm_backlight_exit(scon); - drm_connector_unregister(connector); - drm_connector_cleanup(connector); -} - -static const struct drm_connector_funcs connector_funcs = { - .dpms = drm_helper_connector_dpms, - .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = shmob_drm_connector_destroy, -}; - -int shmob_drm_connector_create(struct shmob_drm_device *sdev, - struct drm_encoder *encoder) -{ - struct drm_connector *connector = &sdev->connector.connector; - int ret; - - sdev->connector.encoder = encoder; - - connector->display_info.width_mm = sdev->pdata->panel.width_mm; - connector->display_info.height_mm = sdev->pdata->panel.height_mm; - - ret = drm_connector_init(sdev->ddev, connector, &connector_funcs, - DRM_MODE_CONNECTOR_LVDS); - if (ret < 0) - return ret; - - drm_connector_helper_add(connector, &connector_helper_funcs); - - ret = shmob_drm_backlight_init(&sdev->connector); - if (ret < 0) - goto err_cleanup; - - ret = drm_connector_attach_encoder(connector, encoder); - if (ret < 0) - goto err_backlight; - - drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); - drm_object_property_set_value(&connector->base, - sdev->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF); - - return 0; - -err_backlight: - shmob_drm_backlight_exit(&sdev->connector); -err_cleanup: - drm_connector_cleanup(connector); - return ret; -} |
