diff options
Diffstat (limited to 'drivers')
52 files changed, 3748 insertions, 866 deletions
diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig index 5d390abef6bd..cf95a8b9b68d 100644 --- a/drivers/gpu/drm/msm/Kconfig +++ b/drivers/gpu/drm/msm/Kconfig @@ -93,6 +93,16 @@ config DRM_SDE_WB help Choose this option for writeback connector support. +config DRM_SDE_SHD + bool "Enable Shared display support in SDE DRM" + depends on DRM_MSM + help + Choose this option for shared display support. + This option enables multiple logical displays + to share one base physical encoder/connector. + Each logical display will appear as different + connectors and report back to user. + config DRM_SDE_HDMI bool "Enable HDMI driver support in DRM SDE driver" depends on DRM_MSM diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index f8758e063146..6edbca08536f 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -136,6 +136,9 @@ msm_drm-$(CONFIG_DRM_MSM) += \ msm_drm-$(CONFIG_DRM_SDE_WB) += sde/sde_wb.o \ sde/sde_encoder_phys_wb.o +msm_drm-$(CONFIG_DRM_SDE_SHD) += sde/sde_shd.o \ + sde/sde_encoder_phys_shd.o + msm_drm-$(CONFIG_DRM_MSM) += \ msm_atomic.o \ msm_drv.o \ diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_display.c b/drivers/gpu/drm/msm/dsi-staging/dsi_display.c index 175603b85d49..742760cc791c 100644 --- a/drivers/gpu/drm/msm/dsi-staging/dsi_display.c +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_display.c @@ -1142,6 +1142,8 @@ static int dsi_display_parse_dt(struct dsi_display *display) int i, size; u32 phy_count = 0; struct device_node *of_node; + const char *name; + u32 top = 0; /* Parse controllers */ for (i = 0; i < MAX_DSI_CTRLS_PER_DISPLAY; i++) { @@ -1189,6 +1191,24 @@ static int dsi_display_parse_dt(struct dsi_display *display) "qcom,dsi-split-swap"); } + rc = of_property_read_string(display->pdev->dev.of_node, + "qcom,display-topology-control", + &name); + if (rc) { + SDE_ERROR("unable to get qcom,display-topology-control,rc=%d\n", + rc); + } else { + SDE_DEBUG("%s qcom,display-topology-control = %s\n", + __func__, name); + + if (!strcmp(name, "force-mixer")) + top = BIT(SDE_RM_TOPCTL_FORCE_MIXER); + else if (!strcmp(name, "force-tiling")) + top = BIT(SDE_RM_TOPCTL_FORCE_TILING); + + display->display_topology = top; + } + if (of_get_property(display->pdev->dev.of_node, "qcom,dsi-panel", &size)) { display->panel_count = size / sizeof(int); diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_display.h b/drivers/gpu/drm/msm/dsi-staging/dsi_display.h index 25b2d0c1ec53..d285779e07c3 100644 --- a/drivers/gpu/drm/msm/dsi-staging/dsi_display.h +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_display.h @@ -123,6 +123,7 @@ struct dsi_display_clk_info { * @root: Debugfs root directory * @cont_splash_enabled: Early splash status. * @dsi_split_swap: Swap dsi output in split mode. + * @display_topology: user requested display topology */ struct dsi_display { struct platform_device *pdev; @@ -163,6 +164,7 @@ struct dsi_display { bool cont_splash_enabled; bool dsi_split_swap; + u32 display_topology; }; int dsi_display_dev_probe(struct platform_device *pdev); diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c index 35000d7eb12a..ad9e553785b6 100644 --- a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c @@ -288,6 +288,29 @@ static const struct drm_bridge_funcs dsi_bridge_ops = { .mode_set = dsi_bridge_mode_set, }; +int dsi_display_set_top_ctl(struct drm_connector *connector, + struct drm_display_mode *adj_mode, void *display) +{ + int rc = 0; + struct dsi_display *dsi_display = (struct dsi_display *)display; + + if (!dsi_display) { + SDE_ERROR("dsi_display is NULL\n"); + return -EINVAL; + } + + if (dsi_display->display_topology) { + SDE_DEBUG("%s, set display topology %d\n", + __func__, dsi_display->display_topology); + + msm_property_set_property(sde_connector_get_propinfo(connector), + sde_connector_get_property_values(connector->state), + CONNECTOR_PROP_TOPOLOGY_CONTROL, + dsi_display->display_topology); + } + return rc; +} + int dsi_conn_post_init(struct drm_connector *connector, void *info, void *display) diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.h b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.h index 934899bd2068..89ad0da21946 100644 --- a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.h +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Copyright (c) 2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -33,6 +33,16 @@ struct dsi_bridge { }; /** + * dsi_display_set_top_ctl - callback to set display topology property + * @connector: Pointer to drm connector structure + * @adj_mode: adjusted mode + * @display: Pointer to private display handle + * Returns: Zero on success + */ +int dsi_display_set_top_ctl(struct drm_connector *connector, + struct drm_display_mode *adj_mode, void *display); + +/** * dsi_conn_post_init - callback to perform additional initialization steps * @connector: Pointer to drm connector structure * @info: Pointer to sde connector info structure diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c index ca60c869d613..ac9c81c5eda1 100644 --- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c @@ -2520,6 +2520,30 @@ end: return rc; } +int sde_hdmi_set_top_ctl(struct drm_connector *connector, + struct drm_display_mode *adj_mode, void *display) +{ + int rc = 0; + struct sde_hdmi *sde_hdmi = (struct sde_hdmi *)display; + + if (!sde_hdmi) { + SDE_ERROR("sde_hdmi is NULL\n"); + return -EINVAL; + } + + if (sde_hdmi->display_topology) { + SDE_DEBUG("%s, set display topology %d\n", + __func__, sde_hdmi->display_topology); + + msm_property_set_property(sde_connector_get_propinfo(connector), + sde_connector_get_property_values(connector->state), + CONNECTOR_PROP_TOPOLOGY_CONTROL, + sde_hdmi->display_topology); + } + + return rc; +} + int sde_hdmi_connector_post_init(struct drm_connector *connector, void *info, void *display) @@ -3120,6 +3144,9 @@ static int _sde_hdmi_parse_dt(struct device_node *node, { int rc = 0; + const char *name; + u32 top = 0; + display->name = of_get_property(node, "label", NULL); display->display_type = of_get_property(node, @@ -3130,6 +3157,23 @@ static int _sde_hdmi_parse_dt(struct device_node *node, display->non_pluggable = of_property_read_bool(node, "qcom,non-pluggable"); + rc = of_property_read_string(node, "qcom,display-topology-control", + &name); + if (rc) { + SDE_ERROR("unable to get qcom,display-topology-control,rc=%d\n", + rc); + } else { + SDE_DEBUG("%s qcom,display-topology-control = %s\n", + __func__, name); + + if (!strcmp(name, "force-mixer")) + top = BIT(SDE_RM_TOPCTL_FORCE_MIXER); + else if (!strcmp(name, "force-tiling")) + top = BIT(SDE_RM_TOPCTL_FORCE_TILING); + + display->display_topology = top; + } + display->skip_ddc = of_property_read_bool(node, "qcom,skip_ddc"); diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h index 9a15f40bb32c..082b3328a40d 100644 --- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h @@ -103,6 +103,7 @@ enum hdmi_tx_feature_type { * @display_lock: Mutex for sde_hdmi interface. * @ctrl: Controller information for HDMI display. * @non_pluggable: If HDMI display is non pluggable + * @display_topology: user requested display topology * @num_of_modes: Number of modes supported by display if non pluggable. * @mode_list: Mode list if non pluggable. * @mode: Current display mode. @@ -154,6 +155,7 @@ struct sde_hdmi { struct sde_edid_ctrl *edid_ctrl; bool non_pluggable; + u32 display_topology; bool skip_ddc; u32 num_of_modes; struct list_head mode_list; @@ -280,6 +282,17 @@ int sde_hdmi_connector_pre_deinit(struct drm_connector *connector, void *display); /** + * sde_hdmi_set_top_ctl()- set display topology control property + * @connector: Pointer to drm connector structure + * @adj_mode: adjusted mode + * @display: Pointer to private display handle + * + * Return: error code + */ +int sde_hdmi_set_top_ctl(struct drm_connector *connector, + struct drm_display_mode *adj_mode, void *display); + +/** * sde_hdmi_connector_post_init()- perform additional initialization steps * @connector: Pointer to drm connector structure * @info: Pointer to sde connector info structure @@ -570,6 +583,12 @@ static inline int sde_hdmi_connector_pre_deinit(struct drm_connector *connector, return 0; } +static inline int sde_hdmi_set_top_ctl(struct drm_connector *connector, + struct drm_display_mode *adj_mode, void *display) +{ + return 0; +} + static inline int sde_hdmi_connector_post_init(struct drm_connector *connector, void *info, void *display) diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index c2f5621ddf8b..ad33e2fa11bb 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. * Copyright (C) 2013 Red Hat * Author: Rob Clark <robdclark@gmail.com> * @@ -2134,10 +2134,150 @@ static int msm_pm_resume(struct device *dev) return 0; } + +static int msm_pm_freeze(struct device *dev) +{ + struct drm_device *ddev; + struct drm_crtc *crtc; + struct drm_modeset_acquire_ctx *ctx; + struct drm_atomic_state *state; + struct msm_drm_private *priv; + struct msm_kms *kms; + int early_display = 0; + int ret = 0; + + if (!dev) + return -EINVAL; + + ddev = dev_get_drvdata(dev); + if (!ddev || !ddev->dev_private) + return -EINVAL; + + priv = ddev->dev_private; + + kms = priv->kms; + if (kms && kms->funcs && kms->funcs->early_display_status) + early_display = kms->funcs->early_display_status(kms); + + SDE_EVT32(0); + + if (early_display) { + /* acquire modeset lock(s) */ + drm_modeset_lock_all(ddev); + ctx = ddev->mode_config.acquire_ctx; + + /* save current state for restore */ + if (priv->suspend_state) + drm_atomic_state_free(priv->suspend_state); + + priv->suspend_state = + drm_atomic_helper_duplicate_state(ddev, ctx); + + if (IS_ERR_OR_NULL(priv->suspend_state)) { + DRM_ERROR("failed to back up suspend state\n"); + priv->suspend_state = NULL; + goto unlock; + } + + /* create atomic null state to idle CRTCs */ + state = drm_atomic_state_alloc(ddev); + if (IS_ERR_OR_NULL(state)) { + DRM_ERROR("failed to allocate null atomic state\n"); + goto unlock; + } + + state->acquire_ctx = ctx; + + /* commit the null state */ + ret = drm_atomic_commit(state); + if (ret < 0) { + DRM_ERROR("failed to commit null state, %d\n", ret); + drm_atomic_state_free(state); + } + + drm_for_each_crtc(crtc, ddev) + drm_crtc_vblank_off(crtc); + +unlock: + drm_modeset_unlock_all(ddev); + } else { + ret = msm_pm_suspend(dev); + if (ret) + return ret; + } + return 0; +} + +static int msm_pm_restore(struct device *dev) +{ + struct drm_device *ddev; + struct drm_crtc *crtc; + struct msm_drm_private *priv; + struct msm_kms *kms; + int early_display = 0; + int ret; + + if (!dev) + return -EINVAL; + + ddev = dev_get_drvdata(dev); + if (!ddev || !ddev->dev_private) + return -EINVAL; + + priv = ddev->dev_private; + + kms = priv->kms; + if (kms && kms->funcs && kms->funcs->early_display_status) + early_display = kms->funcs->early_display_status(kms); + + + SDE_EVT32(priv->suspend_state != NULL); + + if (early_display) { + drm_mode_config_reset(ddev); + + drm_modeset_lock_all(ddev); + + drm_for_each_crtc(crtc, ddev) + drm_crtc_vblank_on(crtc); + + if (priv->suspend_state) { + priv->suspend_state->acquire_ctx = + ddev->mode_config.acquire_ctx; + + ret = drm_atomic_commit(priv->suspend_state); + if (ret < 0) { + DRM_ERROR("failed to restore state, %d\n", ret); + drm_atomic_state_free(priv->suspend_state); + } + + priv->suspend_state = NULL; + } + + drm_modeset_unlock_all(ddev); + } else { + ret = msm_pm_resume(dev); + if (ret) + return ret; + } + + return 0; +} + +static int msm_pm_thaw(struct device *dev) +{ + msm_pm_restore(dev); + + return 0; +} #endif static const struct dev_pm_ops msm_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(msm_pm_suspend, msm_pm_resume) + .suspend = msm_pm_suspend, + .resume = msm_pm_resume, + .freeze = msm_pm_freeze, + .restore = msm_pm_restore, + .thaw = msm_pm_thaw, }; static int msm_drm_bind(struct device *dev) diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index e0ac0582e791..87649f7ec3dd 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -196,12 +196,14 @@ enum msm_display_compression { * @MSM_DISPLAY_CAP_CMD_MODE: Command mode supported * @MSM_DISPLAY_CAP_HOT_PLUG: Hot plug detection supported * @MSM_DISPLAY_CAP_EDID: EDID supported + * @MSM_DISPLAY_CAP_SHARED: Display is shared */ enum msm_display_caps { MSM_DISPLAY_CAP_VID_MODE = BIT(0), MSM_DISPLAY_CAP_CMD_MODE = BIT(1), MSM_DISPLAY_CAP_HOT_PLUG = BIT(2), MSM_DISPLAY_CAP_EDID = BIT(3), + MSM_DISPLAY_CAP_SHARED = BIT(4), }; /** diff --git a/drivers/gpu/drm/msm/msm_kms.h b/drivers/gpu/drm/msm/msm_kms.h index ed0ba928f170..6e3df60aac55 100644 --- a/drivers/gpu/drm/msm/msm_kms.h +++ b/drivers/gpu/drm/msm/msm_kms.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2017, 2019, The Linux Foundation. All rights reserved. * Copyright (C) 2013 Red Hat * Author: Rob Clark <robdclark@gmail.com> * @@ -98,6 +98,7 @@ struct msm_kms_funcs { struct drm_encoder *slave_encoder, bool is_cmd_mode); void (*postopen)(struct msm_kms *kms, struct drm_file *file); + bool (*early_display_status)(struct msm_kms *kms); /* cleanup: */ void (*preclose)(struct msm_kms *kms, struct drm_file *file); void (*postclose)(struct msm_kms *kms, struct drm_file *file); diff --git a/drivers/gpu/drm/msm/sde/sde_connector.c b/drivers/gpu/drm/msm/sde/sde_connector.c index 1bc3d0a926eb..7930cc29f7f4 100644 --- a/drivers/gpu/drm/msm/sde/sde_connector.c +++ b/drivers/gpu/drm/msm/sde/sde_connector.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -11,6 +11,8 @@ */ #define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ +#include <linux/suspend.h> + #include "msm_drv.h" #include "sde_kms.h" @@ -36,7 +38,8 @@ static const struct drm_prop_enum_list e_topology_control[] = { {SDE_RM_TOPCTL_RESERVE_CLEAR, "reserve_clear"}, {SDE_RM_TOPCTL_DSPP, "dspp"}, {SDE_RM_TOPCTL_FORCE_TILING, "force_tiling"}, - {SDE_RM_TOPCTL_PPSPLIT, "ppsplit"} + {SDE_RM_TOPCTL_PPSPLIT, "ppsplit"}, + {SDE_RM_TOPCTL_FORCE_MIXER, "force_mixer"} }; static const struct drm_prop_enum_list e_power_mode[] = { @@ -585,11 +588,16 @@ void sde_connector_complete_commit(struct drm_connector *connector) /* signal connector's retire fence */ sde_fence_signal(&to_sde_connector(connector)->retire_fence, 0); - /* - * After LK totally exits, LK's early splash resource - * should be released. + /* If below both 2 conditions are met, LK's early splash resources + * should be freed. + * 1) When get_hibernation_status() is returned as true. + * a. hibernation image snapshot failed. + * b. hibernation restore successful. + * c. hibernation restore failed. + * 2) After LK totally exits. */ - if (sde_splash_get_lk_complete_status(priv->kms)) { + if (get_hibernation_status() && + sde_splash_get_lk_complete_status(priv->kms)) { c_conn = to_sde_connector(connector); sde_splash_free_resource(priv->kms, &priv->phandle, diff --git a/drivers/gpu/drm/msm/sde/sde_connector.h b/drivers/gpu/drm/msm/sde/sde_connector.h index 6433d3f3bca4..5f44fb7bf094 100644 --- a/drivers/gpu/drm/msm/sde/sde_connector.h +++ b/drivers/gpu/drm/msm/sde/sde_connector.h @@ -124,6 +124,16 @@ struct sde_connector_ops { */ int (*get_info)(struct msm_display_info *info, void *display); + /** + * set_topology_ctl - set sde display topology property + * @connector: Pointer to drm connector structure + * @adj_mode: adjusted mode + * @display: Pointer to private display structure + * Returns: Zero on success + */ + int (*set_topology_ctl)(struct drm_connector *connector, + struct drm_display_mode *adj_mode, void *display); + int (*set_backlight)(void *display, u32 bl_lvl); @@ -192,6 +202,8 @@ struct sde_connector_ops { * @property_data: Array of private data for generic property handling * @blob_caps: Pointer to blob structure for 'capabilities' property * @blob_hdr: Pointer to blob structure for 'hdr_properties' property + * @is_shared: connector is shared + * @shared_roi: roi of the shared display */ struct sde_connector { struct drm_connector base; @@ -218,6 +230,8 @@ struct sde_connector { struct msm_property_data property_data[CONNECTOR_PROP_COUNT]; struct drm_property_blob *blob_caps; struct drm_property_blob *blob_hdr; + bool is_shared; + struct sde_rect shared_roi; }; /** diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.c b/drivers/gpu/drm/msm/sde/sde_crtc.c index 86d260e7ca5f..733ff5f686c0 100644 --- a/drivers/gpu/drm/msm/sde/sde_crtc.c +++ b/drivers/gpu/drm/msm/sde/sde_crtc.c @@ -181,6 +181,7 @@ static void _sde_crtc_blend_setup_mixer(struct drm_crtc *crtc, struct sde_format *format; struct sde_hw_ctl *ctl = mixer->hw_ctl; struct sde_hw_stage_cfg *stage_cfg = &sde_crtc->stage_cfg; + struct sde_crtc_state *cstate; u32 flush_mask = 0, crtc_split_width; uint32_t lm_idx = LEFT_MIXER, idx; @@ -194,20 +195,31 @@ static void _sde_crtc_blend_setup_mixer(struct drm_crtc *crtc, drm_atomic_crtc_for_each_plane(plane, crtc) { pstate = to_sde_plane_state(plane->state); + cstate = to_sde_crtc_state(crtc->state); - /* always stage plane on either left or right lm */ - if (plane->state->crtc_x >= crtc_split_width) { - lm_idx = RIGHT_MIXER; - idx = right_crtc_zpos_cnt[pstate->stage]++; - } else { + /* shared dual mixer mode will always enable both LM */ + if (cstate->is_shared && + sde_crtc->num_mixers == CRTC_DUAL_MIXERS) { lm_idx = LEFT_MIXER; idx = left_crtc_zpos_cnt[pstate->stage]++; - } + lm_right = true; + } else { + /* always stage plane on either left or right lm */ + if (plane->state->crtc_x >= crtc_split_width) { + lm_idx = RIGHT_MIXER; + idx = right_crtc_zpos_cnt[pstate->stage]++; + } else { + lm_idx = LEFT_MIXER; + idx = left_crtc_zpos_cnt[pstate->stage]++; + } - /* stage plane on right LM if it crosses the boundary */ - lm_right = (lm_idx == LEFT_MIXER) && - (plane->state->crtc_x + plane->state->crtc_w > + /* stage plane on right LM if it crosses the + * boundary. + */ + lm_right = (lm_idx == LEFT_MIXER) && + (plane->state->crtc_x + plane->state->crtc_w > crtc_split_width); + } /* * program each mixer with two hw pipes in dual mixer mode, @@ -780,6 +792,24 @@ static void _sde_crtc_setup_mixers(struct drm_crtc *crtc) mutex_unlock(&sde_crtc->crtc_lock); } +static void _sde_crtc_setup_is_shared(struct drm_crtc_state *state) +{ + struct sde_crtc_state *cstate; + + cstate = to_sde_crtc_state(state); + + cstate->is_shared = false; + if (cstate->num_connectors) { + struct drm_connector *conn = cstate->connectors[0]; + struct sde_connector *sde_conn = to_sde_connector(conn); + + if (sde_conn->is_shared) { + cstate->is_shared = true; + cstate->shared_roi = sde_conn->shared_roi; + } + } +} + static void sde_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { @@ -803,8 +833,10 @@ static void sde_crtc_atomic_begin(struct drm_crtc *crtc, sde_crtc = to_sde_crtc(crtc); dev = crtc->dev; - if (!sde_crtc->num_mixers) + if (!sde_crtc->num_mixers) { + _sde_crtc_setup_is_shared(crtc->state); _sde_crtc_setup_mixers(crtc); + } /* Reset flush mask from previous commit */ for (i = 0; i < ARRAY_SIZE(sde_crtc->mixers); i++) { @@ -1366,6 +1398,7 @@ static int sde_crtc_atomic_check(struct drm_crtc *crtc, state->mode_changed = true; mixer_width = sde_crtc_mixer_width(sde_crtc, mode); + _sde_crtc_setup_is_shared(state); /* get plane state for all drm planes associated with crtc state */ drm_atomic_crtc_state_for_each_plane(plane, state) { diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.h b/drivers/gpu/drm/msm/sde/sde_crtc.h index a1042390b1a9..be3ff7072f60 100644 --- a/drivers/gpu/drm/msm/sde/sde_crtc.h +++ b/drivers/gpu/drm/msm/sde/sde_crtc.h @@ -154,6 +154,8 @@ struct sde_crtc { * @input_fence_timeout_ns : Cached input fence timeout, in ns * @property_blobs: Reference pointers for blob properties * @new_perf: new performance state being requested + * @is_shared: connector is shared + * @shared_roi: roi of the shared display */ struct sde_crtc_state { struct drm_crtc_state base; @@ -168,6 +170,8 @@ struct sde_crtc_state { struct drm_property_blob *property_blobs[CRTC_PROP_COUNT]; struct sde_core_perf_params new_perf; + bool is_shared; + struct sde_rect shared_roi; }; #define to_sde_crtc_state(x) \ diff --git a/drivers/gpu/drm/msm/sde/sde_encoder.c b/drivers/gpu/drm/msm/sde/sde_encoder.c index dde742014125..cddbb19c7504 100644 --- a/drivers/gpu/drm/msm/sde/sde_encoder.c +++ b/drivers/gpu/drm/msm/sde/sde_encoder.c @@ -196,6 +196,8 @@ struct sde_encoder_virt { atomic_t last_underrun_ts; atomic_t underrun_cnt_dwork; struct delayed_work dwork; + + bool is_shared; }; #define to_sde_encoder_virt(x) container_of(x, struct sde_encoder_virt, base) @@ -326,6 +328,7 @@ static int sde_encoder_virt_atomic_check( struct sde_kms *sde_kms; const struct drm_display_mode *mode; struct drm_display_mode *adj_mode; + struct sde_connector *sde_conn = NULL; int i = 0; int ret = 0; @@ -362,6 +365,13 @@ static int sde_encoder_virt_atomic_check( } } + sde_conn = to_sde_connector(conn_state->connector); + if (sde_conn) { + if (sde_conn->ops.set_topology_ctl) + sde_conn->ops.set_topology_ctl(conn_state->connector, + adj_mode, sde_conn->display); + } + /* Reserve dynamic resources now. Indicating AtomicTest phase */ if (!ret) ret = sde_rm_reserve(&sde_kms->rm, drm_enc, crtc_state, @@ -384,6 +394,7 @@ static void sde_encoder_virt_mode_set(struct drm_encoder *drm_enc, struct sde_kms *sde_kms; struct list_head *connector_list; struct drm_connector *conn = NULL, *conn_iter; + struct sde_connector *sde_conn = NULL; struct sde_rm_hw_iter pp_iter; int i = 0, ret; @@ -413,6 +424,13 @@ static void sde_encoder_virt_mode_set(struct drm_encoder *drm_enc, return; } + sde_conn = to_sde_connector(conn); + if (sde_conn) { + if (sde_conn->ops.set_topology_ctl) + sde_conn->ops.set_topology_ctl(conn, + adj_mode, sde_conn->display); + } + /* Reserve dynamic resources now. Indicating non-AtomicTest phase */ ret = sde_rm_reserve(&sde_kms->rm, drm_enc, drm_enc->crtc->state, conn->state, false); @@ -434,7 +452,7 @@ static void sde_encoder_virt_mode_set(struct drm_encoder *drm_enc, struct sde_encoder_phys *phys = sde_enc->phys_encs[i]; if (phys) { - if (!sde_enc->hw_pp[i]) { + if (!sde_enc->hw_pp[i] && !sde_enc->is_shared) { SDE_ERROR_ENC(sde_enc, "invalid pingpong block for the encoder\n"); return; @@ -1278,6 +1296,33 @@ static int sde_encoder_virt_add_phys_enc_wb(struct sde_encoder_virt *sde_enc, return 0; } +static int sde_encoder_virt_add_phys_enc_shd(struct sde_encoder_virt *sde_enc, + struct sde_enc_phys_init_params *params) +{ + struct sde_encoder_phys *enc = NULL; + + if (sde_enc->num_phys_encs + 1 >= ARRAY_SIZE(sde_enc->phys_encs)) { + SDE_ERROR_ENC(sde_enc, "too many physical encoders %d\n", + sde_enc->num_phys_encs); + return -EINVAL; + } + + enc = sde_encoder_phys_shd_init(params); + + if (IS_ERR(enc)) { + SDE_ERROR_ENC(sde_enc, "failed to init shd enc: %ld\n", + PTR_ERR(enc)); + return PTR_ERR(enc); + } + + sde_enc->is_shared = true; + + sde_enc->phys_encs[sde_enc->num_phys_encs] = enc; + ++sde_enc->num_phys_encs; + + return 0; +} + static int sde_encoder_setup_display(struct sde_encoder_virt *sde_enc, struct sde_kms *sde_kms, struct msm_display_info *disp_info, @@ -1348,7 +1393,10 @@ static int sde_encoder_setup_display(struct sde_encoder_virt *sde_enc, SDE_DEBUG("h_tile_instance %d = %d, split_role %d\n", i, controller_id, phys_params.split_role); - if (intf_type == INTF_WB) { + if (disp_info->capabilities & MSM_DISPLAY_CAP_SHARED) { + phys_params.wb_idx = WB_MAX; + phys_params.intf_idx = controller_id + INTF_0; + } else if (intf_type == INTF_WB) { phys_params.intf_idx = INTF_MAX; phys_params.wb_idx = sde_encoder_get_wb( sde_kms->catalog, @@ -1373,7 +1421,10 @@ static int sde_encoder_setup_display(struct sde_encoder_virt *sde_enc, } if (!ret) { - if (intf_type == INTF_WB) + if (disp_info->capabilities & MSM_DISPLAY_CAP_SHARED) { + ret = sde_encoder_virt_add_phys_enc_shd(sde_enc, + &phys_params); + } else if (intf_type == INTF_WB) ret = sde_encoder_virt_add_phys_enc_wb(sde_enc, &phys_params); else diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys.h b/drivers/gpu/drm/msm/sde/sde_encoder_phys.h index aec844d640bd..089083816c50 100644 --- a/drivers/gpu/drm/msm/sde/sde_encoder_phys.h +++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys.h @@ -298,6 +298,27 @@ struct sde_encoder_phys_wb { }; /** + * struct sde_encoder_phys_shd - sub-class of sde_encoder_phys to handle shared + * display + * @base: Baseclass physical encoder structure + * @hw_lm: mixer hw block to overwrite base encoder + * @hw_ctl: ctl hw block to overwrite base encoder + * @irq_idx: IRQ interface lookup index + * @irq_cb: interrupt callback + * @num_mixers: Number of mixers available in base encoder + * @num_ctls: Number of ctls available in base encoder + */ +struct sde_encoder_phys_shd { + struct sde_encoder_phys base; + struct sde_hw_mixer *hw_lm[CRTC_DUAL_MIXERS]; + struct sde_hw_ctl *hw_ctl[CRTC_DUAL_MIXERS]; + int irq_idx[INTR_IDX_MAX]; + struct sde_irq_callback irq_cb[INTR_IDX_MAX]; + u32 num_mixers; + u32 num_ctls; +}; + +/** * struct sde_enc_phys_init_params - initialization parameters for phys encs * @sde_kms: Pointer to the sde_kms top level * @parent: Pointer to the containing virtual encoder @@ -350,6 +371,23 @@ struct sde_encoder_phys *sde_encoder_phys_wb_init( } #endif +/** + * sde_encoder_phys_shd_init - Construct a new shared physical encoder + * @p: Pointer to init params structure + * Return: Error code or newly allocated encoder + */ +#ifdef CONFIG_DRM_SDE_SHD +struct sde_encoder_phys *sde_encoder_phys_shd_init( + struct sde_enc_phys_init_params *p); +#else +static inline +struct sde_encoder_phys *sde_encoder_phys_shd_init( + struct sde_enc_phys_init_params *p) +{ + return NULL; +} +#endif + void sde_encoder_phys_setup_cdm(struct sde_encoder_phys *phys_enc, const struct sde_format *format, u32 output_type, struct sde_rect *roi); diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_shd.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_shd.c new file mode 100644 index 000000000000..2eb25e9d38f0 --- /dev/null +++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_shd.c @@ -0,0 +1,1004 @@ +/* + * Copyright (c) 2018 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#define pr_fmt(fmt) "[drm-shd:%s:%d] " fmt, __func__, __LINE__ + +#include <linux/debugfs.h> +#include <uapi/drm/sde_drm.h> + +#include "sde_encoder_phys.h" +#include "sde_formats.h" +#include "sde_hw_top.h" +#include "sde_hw_interrupts.h" +#include "sde_core_irq.h" +#include "sde_crtc.h" +#include "sde_trace.h" +#include "sde_shd.h" +#include "sde_plane.h" + +#define SHD_DEBUG(fmt, ...) pr_debug(fmt, ##__VA_ARGS__) + +#define SDE_ERROR_PHYS(p, fmt, ...) SDE_ERROR("enc%d intf%d " fmt,\ + (p) ? (p)->parent->base.id : -1, \ + (p) ? (p)->intf_idx - INTF_0 : -1, \ + ##__VA_ARGS__) + +#define SDE_DEBUG_PHYS(p, fmt, ...) SDE_DEBUG("enc%d intf%d " fmt,\ + (p) ? (p)->parent->base.id : -1, \ + (p) ? (p)->intf_idx - INTF_0 : -1, \ + ##__VA_ARGS__) + +#define CTL_SSPP_FLUSH_MASK 0xCC183F +#define CTL_MIXER_FLUSH_MASK 0x1007C0 + +#define CTL_LAYER(lm) \ + (((lm) == LM_5) ? (0x024) : (((lm) - LM_0) * 0x004)) +#define CTL_LAYER_EXT(lm) \ + (0x40 + (((lm) - LM_0) * 0x004)) +#define CTL_LAYER_EXT2(lm) \ + (0x70 + (((lm) - LM_0) * 0x004)) + +#define CTL_MIXER_BORDER_OUT BIT(24) + +#define LM_BLEND0_OP 0x00 + +static inline struct sde_encoder_phys_shd *to_sde_encoder_phys_shd( + struct sde_encoder_phys *phys_enc) +{ + return container_of(phys_enc, struct sde_encoder_phys_shd, base); +} + +static DEFINE_SPINLOCK(hw_ctl_lock); + +struct sde_shd_ctl_mixer_cfg { + u32 mixercfg; + u32 mixercfg_ext; + u32 mixercfg_ext2; + + u32 mixercfg_mask; + u32 mixercfg_ext_mask; + u32 mixercfg_ext2_mask; +}; + +struct sde_shd_hw_ctl { + struct sde_hw_ctl base; + struct shd_stage_range range; + struct sde_hw_ctl *orig; + u32 flush_mask; + struct sde_shd_ctl_mixer_cfg mixer_cfg[MAX_BLOCKS]; + struct sde_encoder_phys_shd *shd_enc; +}; + +struct sde_shd_mixer_cfg { + uint32_t fg_alpha; + uint32_t bg_alpha; + uint32_t blend_op; + bool dirty; +}; + +struct sde_shd_hw_mixer { + struct sde_hw_mixer base; + struct shd_stage_range range; + struct sde_hw_mixer *orig; + struct sde_shd_mixer_cfg cfg[SDE_STAGE_MAX]; +}; + +static bool sde_encoder_phys_shd_is_master(struct sde_encoder_phys *phys_enc) +{ + return true; +} + +static void sde_encoder_phys_shd_vblank_irq(void *arg, int irq_idx) +{ + struct sde_encoder_phys *phys_enc = arg; + struct sde_hw_ctl *hw_ctl; + struct sde_shd_hw_ctl *shd_ctl; + unsigned long lock_flags; + u32 flush_register = ~0; + int new_cnt = -1, old_cnt = -1; + + if (!phys_enc) + return; + + hw_ctl = phys_enc->hw_ctl; + if (!hw_ctl) + return; + + if (phys_enc->parent_ops.handle_vblank_virt) + phys_enc->parent_ops.handle_vblank_virt(phys_enc->parent, + phys_enc); + + old_cnt = atomic_read(&phys_enc->pending_kickoff_cnt); + + /* + * only decrement the pending flush count if we've actually flushed + * hardware. due to sw irq latency, vblank may have already happened + * so we need to double-check with hw that it accepted the flush bits + */ + spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags); + + if (hw_ctl && hw_ctl->ops.get_flush_register) + flush_register = hw_ctl->ops.get_flush_register(hw_ctl); + + shd_ctl = container_of(hw_ctl, struct sde_shd_hw_ctl, base); + + if ((flush_register & shd_ctl->flush_mask) == 0) + new_cnt = atomic_add_unless(&phys_enc->pending_kickoff_cnt, + -1, 0); + + spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags); + + SDE_EVT32_IRQ(DRMID(phys_enc->parent), phys_enc->intf_idx - INTF_0, + old_cnt, new_cnt, flush_register); + + /* Signal any waiting atomic commit thread */ + wake_up_all(&phys_enc->pending_kickoff_wq); +} + +static int _sde_encoder_phys_shd_register_irq( + struct sde_encoder_phys *phys_enc, + enum sde_intr_type intr_type, int idx, + void (*irq_func)(void *, int), const char *irq_name) +{ + struct sde_encoder_phys_shd *shd_enc; + int ret = 0; + + shd_enc = to_sde_encoder_phys_shd(phys_enc); + shd_enc->irq_idx[idx] = sde_core_irq_idx_lookup(phys_enc->sde_kms, + intr_type, phys_enc->intf_idx); + + if (shd_enc->irq_idx[idx] < 0) { + SDE_DEBUG_PHYS(phys_enc, + "failed to lookup IRQ index for %s type:%d\n", irq_name, + intr_type); + return -EINVAL; + } + + shd_enc->irq_cb[idx].func = irq_func; + shd_enc->irq_cb[idx].arg = phys_enc; + ret = sde_core_irq_register_callback(phys_enc->sde_kms, + shd_enc->irq_idx[idx], &shd_enc->irq_cb[idx]); + if (ret) { + SDE_ERROR_PHYS(phys_enc, + "failed to register IRQ callback for %s\n", irq_name); + shd_enc->irq_idx[idx] = -EINVAL; + return ret; + } + + SDE_DEBUG_PHYS(phys_enc, "registered irq %s idx: %d\n", + irq_name, shd_enc->irq_idx[idx]); + + return ret; +} + +static int _sde_encoder_phys_shd_unregister_irq( + struct sde_encoder_phys *phys_enc, int idx) +{ + struct sde_encoder_phys_shd *shd_enc; + + shd_enc = to_sde_encoder_phys_shd(phys_enc); + + sde_core_irq_unregister_callback(phys_enc->sde_kms, + shd_enc->irq_idx[idx], &shd_enc->irq_cb[idx]); + + SDE_DEBUG_PHYS(phys_enc, "unregistered %d\n", shd_enc->irq_idx[idx]); + + return 0; +} + +static void _sde_shd_hw_ctl_clear_blendstages_in_range( + struct sde_shd_hw_ctl *hw_ctl, enum sde_lm lm) +{ + struct sde_hw_blk_reg_map *c = &hw_ctl->base.hw; + u32 mixercfg, mixercfg_ext; + u32 mixercfg_ext2; + u32 mask = 0, ext_mask = 0, ext2_mask = 0; + u32 start = hw_ctl->range.start + SDE_STAGE_0; + u32 end = start + hw_ctl->range.size; + u32 i; + + mixercfg = SDE_REG_READ(c, CTL_LAYER(lm)); + mixercfg_ext = SDE_REG_READ(c, CTL_LAYER_EXT(lm)); + mixercfg_ext2 = SDE_REG_READ(c, CTL_LAYER_EXT2(lm)); + + if (!mixercfg && !mixercfg_ext && !mixercfg_ext2) + goto end; + + /* SSPP_VIG0 */ + i = (mixercfg & 0x7) | ((mixercfg_ext & 1) << 3); + if (i > start && i <= end) { + mask |= 0x7; + ext_mask |= 0x1; + } + + /* SSPP_VIG1 */ + i = ((mixercfg >> 3) & 0x7) | (((mixercfg_ext >> 2) & 1) << 3); + if (i > start && i <= end) { + mask |= (0x7 << 3); + ext_mask |= (0x1 << 2); + } + + /* SSPP_VIG2 */ + i = ((mixercfg >> 6) & 0x7) | (((mixercfg_ext >> 4) & 1) << 3); + if (i > start && i <= end) { + mask |= (0x7 << 6); + ext_mask |= (0x1 << 4); + } + + /* SSPP_RGB0 */ + i = ((mixercfg >> 9) & 0x7) | (((mixercfg_ext >> 8) & 1) << 3); + if (i > start && i <= end) { + mask |= (0x7 << 9); + ext_mask |= (0x1 << 8); + } + + /* SSPP_RGB1 */ + i = ((mixercfg >> 12) & 0x7) | (((mixercfg_ext >> 10) & 1) << 3); + if (i > start && i <= end) { + mask |= (0x7 << 12); + ext_mask |= (0x1 << 10); + } + + /* SSPP_RGB2 */ + i = ((mixercfg >> 15) & 0x7) | (((mixercfg_ext >> 12) & 1) << 3); + if (i > start && i <= end) { + mask |= (0x7 << 15); + ext_mask |= (0x1 << 12); + } + + /* SSPP_DMA0 */ + i = ((mixercfg >> 18) & 0x7) | (((mixercfg_ext >> 16) & 1) << 3); + if (i > start && i <= end) { + mask |= (0x7 << 18); + ext_mask |= (0x1 << 16); + } + + /* SSPP_DMA1 */ + i = ((mixercfg >> 21) & 0x7) | (((mixercfg_ext >> 18) & 1) << 3); + if (i > start && i <= end) { + mask |= (0x7 << 21); + ext_mask |= (0x1 << 18); + } + + /* SSPP_VIG3 */ + i = ((mixercfg >> 26) & 0x7) | (((mixercfg_ext >> 6) & 1) << 3); + if (i > start && i <= end) { + mask |= (0x7 << 26); + ext_mask |= (0x1 << 6); + } + + /* SSPP_RGB3 */ + i = ((mixercfg >> 29) & 0x7) | (((mixercfg_ext >> 14) & 1) << 3); + if (i > start && i <= end) { + mask |= (0x7 << 29); + ext_mask |= (0x1 << 14); + } + + /* SSPP_CURSOR_0 */ + i = (mixercfg_ext >> 20) & 0xF; + if (i > start && i <= end) + ext_mask |= (0xF << 20); + + /* SSPP_CURSOR_1 */ + i = (mixercfg_ext >> 26) & 0xF; + if (i > start && i <= end) + ext_mask |= (0xF << 26); + + /* SSPP_DMA2 */ + i = (mixercfg_ext2 >> 0) & 0xF; + if (i > start && i <= end) + ext2_mask |= (0xF << 0); + + /* SSPP_DMA3 */ + i = (mixercfg_ext2 >> 4) & 0xF; + if (i > start && i <= end) + ext2_mask |= (0xF << 4); + +end: + hw_ctl->mixer_cfg[lm].mixercfg_mask = mask; + hw_ctl->mixer_cfg[lm].mixercfg_ext_mask = ext_mask; + hw_ctl->mixer_cfg[lm].mixercfg_ext2_mask = ext2_mask; +} + +static void _sde_shd_hw_ctl_clear_all_blendstages(struct sde_hw_ctl *ctx, + bool handoff, const u32 *resv_pipes, u32 resv_pipes_length) +{ + struct sde_shd_hw_ctl *hw_ctl; + int i; + + if (!ctx) + return; + + hw_ctl = container_of(ctx, struct sde_shd_hw_ctl, base); + + for (i = 0; i < ctx->mixer_count; i++) { + int mixer_id = ctx->mixer_hw_caps[i].id; + + _sde_shd_hw_ctl_clear_blendstages_in_range(hw_ctl, mixer_id); + } +} + +static inline int _stage_offset(struct sde_hw_mixer *ctx, enum sde_stage stage) +{ + const struct sde_lm_sub_blks *sblk = ctx->cap->sblk; + int rc; + + if (stage == SDE_STAGE_BASE) + rc = -EINVAL; + else if (stage <= sblk->maxblendstages) + rc = sblk->blendstage_base[stage - SDE_STAGE_0]; + else + rc = -EINVAL; + + return rc; +} + +static void _sde_shd_hw_ctl_setup_blendstage(struct sde_hw_ctl *ctx, + enum sde_lm lm, struct sde_hw_stage_cfg *stage_cfg, u32 index, + bool handoff, const u32 *resv_pipes, u32 resv_pipes_length) +{ + struct sde_shd_hw_ctl *hw_ctl; + u32 mixercfg = 0, mixercfg_ext = 0, mix, ext, full, mixercfg_ext2; + u32 mask = 0, ext_mask = 0, ext2_mask = 0; + int i, j; + int stages; + int stage_offset = 0; + int pipes_per_stage; + + if (!ctx) + return; + + hw_ctl = container_of(ctx, struct sde_shd_hw_ctl, base); + + if (test_bit(SDE_MIXER_SOURCESPLIT, + &ctx->mixer_hw_caps->features)) + pipes_per_stage = PIPES_PER_STAGE; + else + pipes_per_stage = 1; + + _sde_shd_hw_ctl_clear_blendstages_in_range(hw_ctl, lm); + + if (!stage_cfg) + goto exit; + + mixercfg = CTL_MIXER_BORDER_OUT; + stage_offset = hw_ctl->range.start; + stages = hw_ctl->range.size; + + for (i = SDE_STAGE_0; i <= stages; i++) { + /* overflow to ext register if 'i + 1 > 7' */ + mix = (i + stage_offset + 1) & 0x7; + ext = (i + stage_offset) >= 7; + full = (i + stage_offset + 1) & 0xF; + + for (j = 0 ; j < pipes_per_stage; j++) { + switch (stage_cfg->stage[index][i][j]) { + case SSPP_VIG0: + mixercfg |= mix << 0; + mixercfg_ext |= ext << 0; + mask |= 0x7 << 0; + ext_mask |= 0x1 << 0; + break; + case SSPP_VIG1: + mixercfg |= mix << 3; + mixercfg_ext |= ext << 2; + mask |= 0x7 << 3; + ext_mask |= 0x1 << 2; + break; + case SSPP_VIG2: + mixercfg |= mix << 6; + mixercfg_ext |= ext << 4; + mask |= 0x7 << 6; + ext_mask |= 0x1 << 4; + break; + case SSPP_VIG3: + mixercfg |= mix << 26; + mixercfg_ext |= ext << 6; + mask |= 0x7 << 26; + ext_mask |= 0x1 << 6; + break; + case SSPP_RGB0: + mixercfg |= mix << 9; + mixercfg_ext |= ext << 8; + mask |= 0x7 << 9; + ext_mask |= 0x1 << 8; + break; + case SSPP_RGB1: + mixercfg |= mix << 12; + mixercfg_ext |= ext << 10; + mask |= 0x7 << 12; + ext_mask |= 0x1 << 10; + break; + case SSPP_RGB2: + mixercfg |= mix << 15; + mixercfg_ext |= ext << 12; + mask |= 0x7 << 15; + ext_mask |= 0x1 << 12; + break; + case SSPP_RGB3: + mixercfg |= mix << 29; + mixercfg_ext |= ext << 14; + mask |= 0x7 << 29; + ext_mask |= 0x1 << 14; + break; + case SSPP_DMA0: + mixercfg |= mix << 18; + mixercfg_ext |= ext << 16; + mask |= 0x7 << 18; + ext_mask |= 0x1 << 16; + break; + case SSPP_DMA1: + mixercfg |= mix << 21; + mixercfg_ext |= ext << 18; + mask |= 0x7 << 21; + ext_mask |= 0x1 << 18; + break; + case SSPP_DMA2: + mix |= full; + mixercfg_ext2 |= mix << 0; + ext2_mask |= 0xF << 0; + break; + case SSPP_DMA3: + mix |= full; + mixercfg_ext2 |= mix << 4; + ext2_mask |= 0xF << 4; + break; + case SSPP_CURSOR0: + mixercfg_ext |= full << 20; + ext_mask |= 0xF << 20; + break; + case SSPP_CURSOR1: + mixercfg_ext |= full << 26; + ext_mask |= 0xF << 26; + break; + default: + break; + } + } + } + + hw_ctl->mixer_cfg[lm].mixercfg_mask |= mask; + hw_ctl->mixer_cfg[lm].mixercfg_ext_mask |= ext_mask; + hw_ctl->mixer_cfg[lm].mixercfg_ext2_mask |= ext2_mask; +exit: + hw_ctl->mixer_cfg[lm].mixercfg = mixercfg; + hw_ctl->mixer_cfg[lm].mixercfg_ext = mixercfg_ext; + hw_ctl->mixer_cfg[lm].mixercfg_ext2 = mixercfg_ext2; +} + +static void _sde_shd_hw_ctl_trigger_flush(struct sde_hw_ctl *ctx) +{ + struct sde_shd_hw_ctl *hw_ctl; + struct sde_hw_blk_reg_map *c; + u32 mixercfg, mixercfg_ext; + u32 mixercfg_ext2; + int i; + + hw_ctl = container_of(ctx, struct sde_shd_hw_ctl, base); + + hw_ctl->flush_mask = ctx->pending_flush_mask; + + hw_ctl->flush_mask &= CTL_SSPP_FLUSH_MASK; + + c = &ctx->hw; + + for (i = 0; i < ctx->mixer_count; i++) { + int lm = ctx->mixer_hw_caps[i].id; + + mixercfg = SDE_REG_READ(c, CTL_LAYER(lm)); + mixercfg_ext = SDE_REG_READ(c, CTL_LAYER_EXT(lm)); + mixercfg_ext2 = SDE_REG_READ(c, CTL_LAYER_EXT2(lm)); + + mixercfg &= ~hw_ctl->mixer_cfg[lm].mixercfg_mask; + mixercfg_ext &= ~hw_ctl->mixer_cfg[lm].mixercfg_ext_mask; + mixercfg_ext2 &= ~hw_ctl->mixer_cfg[lm].mixercfg_ext2_mask; + + mixercfg |= hw_ctl->mixer_cfg[lm].mixercfg; + mixercfg_ext |= hw_ctl->mixer_cfg[lm].mixercfg_ext; + mixercfg_ext2 |= hw_ctl->mixer_cfg[lm].mixercfg_ext2; + + SDE_REG_WRITE(c, CTL_LAYER(lm), mixercfg); + SDE_REG_WRITE(c, CTL_LAYER_EXT(lm), mixercfg_ext); + SDE_REG_WRITE(c, CTL_LAYER_EXT2(lm), mixercfg_ext2); + } +} + +static void _sde_shd_setup_blend_config(struct sde_hw_mixer *ctx, + uint32_t stage, + uint32_t fg_alpha, uint32_t bg_alpha, uint32_t blend_op) +{ + struct sde_shd_hw_mixer *hw_lm; + struct sde_shd_mixer_cfg *cfg; + + if (!ctx) + return; + + hw_lm = container_of(ctx, struct sde_shd_hw_mixer, base); + + cfg = &hw_lm->cfg[stage + hw_lm->range.start]; + + cfg->fg_alpha = fg_alpha; + cfg->bg_alpha = bg_alpha; + cfg->blend_op = blend_op; + cfg->dirty = true; +} + +static void _sde_shd_setup_mixer_out(struct sde_hw_mixer *ctx, + struct sde_hw_mixer_cfg *cfg) +{ + /* do nothing */ +} + +static void _sde_shd_flush_hw_lm(struct sde_hw_mixer *ctx) +{ + struct sde_shd_hw_mixer *hw_lm; + struct sde_hw_blk_reg_map *c = &ctx->hw; + int stage_off, i; + u32 reset = BIT(16), val; + int start, end; + + if (!ctx) + return; + + hw_lm = container_of(ctx, struct sde_shd_hw_mixer, base); + + start = SDE_STAGE_0 + hw_lm->range.start; + end = start + hw_lm->range.size; + reset = ~reset; + for (i = start; i < end; i++) { + stage_off = _stage_offset(ctx, i); + if (WARN_ON(stage_off < 0)) + return; + + val = SDE_REG_READ(c, LM_BLEND0_OP + stage_off); + val &= reset; + SDE_REG_WRITE(c, LM_BLEND0_OP + stage_off, val); + + if (hw_lm->cfg[i].dirty) { + hw_lm->orig->ops.setup_blend_config(ctx, i, + hw_lm->cfg[i].fg_alpha, + hw_lm->cfg[i].bg_alpha, + hw_lm->cfg[i].blend_op); + hw_lm->cfg[i].dirty = false; + } + } +} + +static void _sde_shd_trigger_flush(struct sde_hw_ctl *ctx) +{ + struct sde_shd_hw_ctl *hw_ctl; + struct sde_encoder_phys_shd *shd_enc; + struct sde_hw_blk_reg_map *c; + unsigned long lock_flags; + int i; + + hw_ctl = container_of(ctx, struct sde_shd_hw_ctl, base); + shd_enc = hw_ctl->shd_enc; + + c = &ctx->hw; + + spin_lock_irqsave(&hw_ctl_lock, lock_flags); + + _sde_shd_hw_ctl_trigger_flush(ctx); + + for (i = 0; i < shd_enc->num_mixers; i++) + _sde_shd_flush_hw_lm(shd_enc->hw_lm[i]); + + hw_ctl->orig->ops.trigger_flush(ctx); + + spin_unlock_irqrestore(&hw_ctl_lock, lock_flags); +} + +static void _sde_encoder_phys_shd_rm_reserve( + struct sde_encoder_phys *phys_enc, + struct shd_display *display) +{ + struct sde_encoder_phys_shd *shd_enc; + struct sde_rm *rm; + struct sde_rm_hw_iter ctl_iter, lm_iter; + struct drm_encoder *encoder; + struct sde_shd_hw_ctl *hw_ctl; + struct sde_shd_hw_mixer *hw_lm; + int i; + + encoder = display->base->encoder; + rm = &phys_enc->sde_kms->rm; + shd_enc = to_sde_encoder_phys_shd(phys_enc); + + sde_rm_init_hw_iter(&ctl_iter, encoder->base.id, SDE_HW_BLK_CTL); + sde_rm_init_hw_iter(&lm_iter, encoder->base.id, SDE_HW_BLK_LM); + + shd_enc->num_mixers = 0; + shd_enc->num_ctls = 0; + + for (i = 0; i < CRTC_DUAL_MIXERS; i++) { + /* reserve layer mixer */ + if (!sde_rm_get_hw(rm, &lm_iter)) + break; + hw_lm = container_of(shd_enc->hw_lm[i], + struct sde_shd_hw_mixer, base); + hw_lm->base = *(struct sde_hw_mixer *)lm_iter.hw; + hw_lm->range = display->stage_range; + hw_lm->orig = lm_iter.hw; + hw_lm->base.ops.setup_blend_config = + _sde_shd_setup_blend_config; + hw_lm->base.ops.setup_mixer_out = + _sde_shd_setup_mixer_out; + + SHD_DEBUG("reserve LM%d %pK from enc %d to %d\n", + hw_lm->base.idx, hw_lm, + DRMID(encoder), + DRMID(phys_enc->parent)); + + sde_rm_ext_blk_create_reserve(rm, + SDE_HW_BLK_LM, 0, + &hw_lm->base, phys_enc->parent); + shd_enc->num_mixers++; + + /* reserve ctl */ + if (!sde_rm_get_hw(rm, &ctl_iter)) + break; + hw_ctl = container_of(shd_enc->hw_ctl[i], + struct sde_shd_hw_ctl, base); + hw_ctl->base = *(struct sde_hw_ctl *)ctl_iter.hw; + hw_ctl->shd_enc = shd_enc; + hw_ctl->range = display->stage_range; + hw_ctl->orig = ctl_iter.hw; + hw_ctl->base.ops.clear_all_blendstages = + _sde_shd_hw_ctl_clear_all_blendstages; + hw_ctl->base.ops.setup_blendstage = + _sde_shd_hw_ctl_setup_blendstage; + hw_ctl->base.ops.trigger_flush = + _sde_shd_trigger_flush; + + SHD_DEBUG("reserve CTL%d %pK from enc %d to %d\n", + hw_ctl->base.idx, hw_ctl, + DRMID(encoder), + DRMID(phys_enc->parent)); + + sde_rm_ext_blk_create_reserve(rm, + SDE_HW_BLK_CTL, 0, + &hw_ctl->base, phys_enc->parent); + shd_enc->num_ctls++; + } +} + +static void _sde_encoder_phys_shd_rm_release( + struct sde_encoder_phys *phys_enc, + struct shd_display *display) +{ + struct sde_rm *rm; + + rm = &phys_enc->sde_kms->rm; + + sde_rm_ext_blk_destroy(rm, phys_enc->parent); +} + +static void sde_encoder_phys_shd_mode_set( + struct sde_encoder_phys *phys_enc, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct drm_connector *connector; + struct sde_connector *sde_conn; + struct shd_display *display; + struct drm_encoder *encoder; + struct sde_rm_hw_iter iter; + struct sde_rm *rm; + + SHD_DEBUG("%d\n", phys_enc->parent->base.id); + + phys_enc->cached_mode = *adj_mode; + + connector = phys_enc->connector; + if (!connector || connector->encoder != phys_enc->parent) { + SDE_ERROR("failed to find connector\n"); + return; + } + + sde_conn = to_sde_connector(connector); + display = sde_conn->display; + encoder = display->base->encoder; + + _sde_encoder_phys_shd_rm_reserve(phys_enc, display); + + rm = &phys_enc->sde_kms->rm; + + sde_rm_init_hw_iter(&iter, DRMID(phys_enc->parent), SDE_HW_BLK_CTL); + if (sde_rm_get_hw(rm, &iter)) + phys_enc->hw_ctl = (struct sde_hw_ctl *)iter.hw; + if (IS_ERR_OR_NULL(phys_enc->hw_ctl)) { + SHD_DEBUG("failed to init ctl, %ld\n", + PTR_ERR(phys_enc->hw_ctl)); + phys_enc->hw_ctl = NULL; + return; + } +} + +static int _sde_encoder_phys_shd_wait_for_vblank( + struct sde_encoder_phys *phys_enc, bool notify) +{ + struct sde_encoder_phys_shd *shd_enc; + u32 irq_status; + int ret = 0; + + if (!phys_enc) { + pr_err("invalid encoder\n"); + return -EINVAL; + } + + if (phys_enc->enable_state != SDE_ENC_ENABLED) { + SDE_ERROR("encoder not enabled\n"); + return -EWOULDBLOCK; + } + + shd_enc = to_sde_encoder_phys_shd(phys_enc); + + /* Wait for kickoff to complete */ + ret = sde_encoder_helper_wait_event_timeout( + DRMID(phys_enc->parent), + phys_enc->intf_idx - INTF_0, + &phys_enc->pending_kickoff_wq, + &phys_enc->pending_kickoff_cnt, + KICKOFF_TIMEOUT_MS); + + if (ret <= 0) { + irq_status = sde_core_irq_read(phys_enc->sde_kms, + INTR_IDX_VSYNC, true); + if (irq_status) { + SDE_EVT32(DRMID(phys_enc->parent), + phys_enc->intf_idx - INTF_0); + SDE_DEBUG_PHYS(phys_enc, "done, irq not triggered\n"); + if (notify && phys_enc->parent_ops.handle_frame_done) + phys_enc->parent_ops.handle_frame_done( + phys_enc->parent, phys_enc, + SDE_ENCODER_FRAME_EVENT_DONE); + sde_encoder_phys_shd_vblank_irq(phys_enc, + INTR_IDX_VSYNC); + ret = 0; + } else { + SDE_EVT32(DRMID(phys_enc->parent), + phys_enc->intf_idx - INTF_0); + SDE_ERROR_PHYS(phys_enc, "kickoff timed out\n"); + if (notify && phys_enc->parent_ops.handle_frame_done) + phys_enc->parent_ops.handle_frame_done( + phys_enc->parent, phys_enc, + SDE_ENCODER_FRAME_EVENT_ERROR); + ret = -ETIMEDOUT; + } + } else { + if (notify && phys_enc->parent_ops.handle_frame_done) + phys_enc->parent_ops.handle_frame_done( + phys_enc->parent, phys_enc, + SDE_ENCODER_FRAME_EVENT_DONE); + ret = 0; + } + + return ret; +} + +static int sde_encoder_phys_shd_wait_for_vblank( + struct sde_encoder_phys *phys_enc) +{ + return _sde_encoder_phys_shd_wait_for_vblank(phys_enc, true); +} + +void sde_encoder_phys_shd_handle_post_kickoff( + struct sde_encoder_phys *phys_enc) +{ + if (!phys_enc) { + SDE_ERROR("invalid encoder\n"); + return; + } + + if (phys_enc->enable_state == SDE_ENC_ENABLING) { + SDE_EVT32(DRMID(phys_enc->parent)); + phys_enc->enable_state = SDE_ENC_ENABLED; + } +} + +static int sde_encoder_phys_shd_control_vblank_irq( + struct sde_encoder_phys *phys_enc, + bool enable) +{ + int ret = 0; + struct sde_encoder_phys_shd *shd_enc; + + if (!phys_enc) { + SDE_ERROR("invalid encoder\n"); + return -EINVAL; + } + + shd_enc = to_sde_encoder_phys_shd(phys_enc); + + SHD_DEBUG("[%pS] %d enable=%d/%d\n", + __builtin_return_address(0), DRMID(phys_enc->parent), + enable, atomic_read(&phys_enc->vblank_refcount)); + + SDE_EVT32(DRMID(phys_enc->parent), enable, + atomic_read(&phys_enc->vblank_refcount)); + + if (enable && atomic_inc_return(&phys_enc->vblank_refcount) == 1) + ret = _sde_encoder_phys_shd_register_irq(phys_enc, + SDE_IRQ_TYPE_INTF_VSYNC, + INTR_IDX_VSYNC, + sde_encoder_phys_shd_vblank_irq, "vsync_irq"); + else if (!enable && atomic_dec_return(&phys_enc->vblank_refcount) == 0) + ret = _sde_encoder_phys_shd_unregister_irq(phys_enc, + INTR_IDX_VSYNC); + + if (ret) + SHD_DEBUG("control vblank irq error %d, enable %d\n", + ret, enable); + + return ret; +} + +static void sde_encoder_phys_shd_enable(struct sde_encoder_phys *phys_enc) +{ + struct drm_connector *connector; + + SHD_DEBUG("%d\n", phys_enc->parent->base.id); + + if (!phys_enc->parent || !phys_enc->parent->dev) { + SDE_ERROR("invalid drm device\n"); + return; + } + + connector = phys_enc->connector; + if (!connector || connector->encoder != phys_enc->parent) { + SDE_ERROR("failed to find connector\n"); + return; + } + + sde_encoder_phys_shd_control_vblank_irq(phys_enc, true); + + if (phys_enc->enable_state == SDE_ENC_DISABLED) + phys_enc->enable_state = SDE_ENC_ENABLING; +} + +static void sde_encoder_phys_shd_disable(struct sde_encoder_phys *phys_enc) +{ + struct sde_connector *sde_conn; + struct shd_display *display; + + SHD_DEBUG("%d\n", phys_enc->parent->base.id); + + if (!phys_enc || !phys_enc->parent || !phys_enc->parent->dev || + !phys_enc->parent->dev->dev_private) { + SDE_ERROR("invalid encoder/device\n"); + return; + } + + if (phys_enc->enable_state == SDE_ENC_DISABLED) { + SDE_ERROR("already disabled\n"); + return; + } + + _sde_shd_hw_ctl_clear_all_blendstages(phys_enc->hw_ctl, + phys_enc->sde_kms->splash_info.handoff, + phys_enc->sde_kms->splash_info.reserved_pipe_info, + MAX_BLOCKS); + + _sde_shd_trigger_flush(phys_enc->hw_ctl); + + _sde_encoder_phys_shd_wait_for_vblank(phys_enc, false); + + sde_encoder_phys_shd_control_vblank_irq(phys_enc, false); + + phys_enc->enable_state = SDE_ENC_DISABLED; + + if (!phys_enc->connector) + return; + + sde_conn = to_sde_connector(phys_enc->connector); + display = sde_conn->display; + + _sde_encoder_phys_shd_rm_release(phys_enc, display); +} + +static void sde_encoder_phys_shd_destroy(struct sde_encoder_phys *phys_enc) +{ + struct sde_encoder_phys_shd *shd_enc = + to_sde_encoder_phys_shd(phys_enc); + + if (!phys_enc) + return; + + kfree(shd_enc); +} + +/** + * sde_encoder_phys_shd_init_ops - initialize writeback operations + * @ops: Pointer to encoder operation table + */ +static void sde_encoder_phys_shd_init_ops(struct sde_encoder_phys_ops *ops) +{ + ops->is_master = sde_encoder_phys_shd_is_master; + ops->mode_set = sde_encoder_phys_shd_mode_set; + ops->enable = sde_encoder_phys_shd_enable; + ops->disable = sde_encoder_phys_shd_disable; + ops->destroy = sde_encoder_phys_shd_destroy; + ops->control_vblank_irq = sde_encoder_phys_shd_control_vblank_irq; + ops->wait_for_commit_done = sde_encoder_phys_shd_wait_for_vblank; + ops->handle_post_kickoff = sde_encoder_phys_shd_handle_post_kickoff; +} + +struct sde_encoder_phys *sde_encoder_phys_shd_init( + struct sde_enc_phys_init_params *p) +{ + struct sde_encoder_phys *phys_enc; + struct sde_encoder_phys_shd *shd_enc; + struct sde_shd_hw_ctl *hw_ctl; + struct sde_shd_hw_mixer *hw_lm; + int ret = 0, i; + + SHD_DEBUG("\n"); + + if (!p || !p->parent) { + SDE_ERROR("invalid params\n"); + ret = -EINVAL; + goto fail_alloc; + } + + shd_enc = kzalloc(sizeof(*shd_enc), GFP_KERNEL); + if (!shd_enc) { + ret = -ENOMEM; + goto fail_alloc; + } + + for (i = 0; i < CRTC_DUAL_MIXERS; i++) { + hw_ctl = kzalloc(sizeof(*hw_ctl), GFP_KERNEL); + if (!hw_ctl) { + ret = -ENOMEM; + goto fail_ctl; + } + shd_enc->hw_ctl[i] = &hw_ctl->base; + + hw_lm = kzalloc(sizeof(*hw_lm), GFP_KERNEL); + if (!hw_lm) { + ret = -ENOMEM; + goto fail_ctl; + } + shd_enc->hw_lm[i] = &hw_lm->base; + } + + phys_enc = &shd_enc->base; + + sde_encoder_phys_shd_init_ops(&phys_enc->ops); + phys_enc->parent = p->parent; + phys_enc->parent_ops = p->parent_ops; + phys_enc->sde_kms = p->sde_kms; + phys_enc->split_role = p->split_role; + phys_enc->intf_mode = INTF_MODE_NONE; + phys_enc->intf_idx = p->intf_idx; + phys_enc->enc_spinlock = p->enc_spinlock; + for (i = 0; i < INTR_IDX_MAX; i++) + INIT_LIST_HEAD(&shd_enc->irq_cb[i].list); + atomic_set(&phys_enc->vblank_refcount, 0); + atomic_set(&phys_enc->pending_kickoff_cnt, 0); + init_waitqueue_head(&phys_enc->pending_kickoff_wq); + phys_enc->enable_state = SDE_ENC_DISABLED; + + return phys_enc; + +fail_ctl: + for (i = 0; i < CRTC_DUAL_MIXERS; i++) { + kfree(shd_enc->hw_ctl[i]); + kfree(shd_enc->hw_lm[i]); + } + kfree(shd_enc); +fail_alloc: + return ERR_PTR(ret); +} + diff --git a/drivers/gpu/drm/msm/sde/sde_kms.c b/drivers/gpu/drm/msm/sde/sde_kms.c index a53b345071a6..44a5f8c4535b 100644 --- a/drivers/gpu/drm/msm/sde/sde_kms.c +++ b/drivers/gpu/drm/msm/sde/sde_kms.c @@ -28,6 +28,7 @@ #include "dsi_drm.h" #include "sde_wb.h" #include "sde_hdmi.h" +#include "sde_shd.h" #include "sde_kms.h" #include "sde_core_irq.h" @@ -364,6 +365,8 @@ static void sde_kms_prepare_commit(struct msm_kms *kms, sde_power_resource_enable(&priv->phandle, sde_kms->core_client, true); + + shd_display_prepare_commit(sde_kms, state); } static void sde_kms_commit(struct msm_kms *kms, @@ -393,6 +396,9 @@ static void sde_kms_complete_commit(struct msm_kms *kms, for_each_crtc_in_state(old_state, crtc, old_crtc_state, i) sde_crtc_complete_commit(crtc, old_crtc_state); + + shd_display_complete_commit(sde_kms, old_state); + sde_power_resource_enable(&priv->phandle, sde_kms->core_client, false); SDE_EVT32(SDE_EVTLOG_FUNC_EXIT); @@ -534,8 +540,25 @@ static int _sde_kms_get_displays(struct sde_kms *sde_kms) sde_kms->hdmi_display_count); } + /* shd */ + sde_kms->shd_displays = NULL; + sde_kms->shd_display_count = shd_display_get_num_of_displays(); + if (sde_kms->shd_display_count) { + sde_kms->shd_displays = kcalloc(sde_kms->shd_display_count, + sizeof(void *), GFP_KERNEL); + if (!sde_kms->shd_displays) + goto exit_deinit_shd; + sde_kms->shd_display_count = + shd_display_get_displays(sde_kms->shd_displays, + sde_kms->shd_display_count); + } + return 0; +exit_deinit_shd: + kfree(sde_kms->shd_displays); + sde_kms->shd_display_count = 0; + sde_kms->shd_displays = NULL; exit_deinit_hdmi: sde_kms->hdmi_display_count = 0; sde_kms->hdmi_displays = NULL; @@ -593,7 +616,8 @@ static int _sde_kms_setup_displays(struct drm_device *dev, .get_modes = dsi_connector_get_modes, .mode_valid = dsi_conn_mode_valid, .get_info = dsi_display_get_info, - .set_backlight = dsi_display_set_backlight + .set_backlight = dsi_display_set_backlight, + .set_topology_ctl = dsi_display_set_top_ctl, }; static const struct sde_connector_ops wb_ops = { .post_init = sde_wb_connector_post_init, @@ -613,7 +637,15 @@ static int _sde_kms_setup_displays(struct drm_device *dev, .get_property = sde_hdmi_get_property, .pre_kickoff = sde_hdmi_pre_kickoff, .mode_needs_full_range = sde_hdmi_mode_needs_full_range, - .get_csc_type = sde_hdmi_get_csc_type + .get_csc_type = sde_hdmi_get_csc_type, + .set_topology_ctl = sde_hdmi_set_top_ctl, + }; + static const struct sde_connector_ops shd_ops = { + .post_init = shd_connector_post_init, + .detect = shd_connector_detect, + .get_modes = shd_connector_get_modes, + .mode_valid = shd_connector_mode_valid, + .get_info = shd_connector_get_info, }; struct msm_display_info info = {0}; struct drm_encoder *encoder; @@ -629,7 +661,8 @@ static int _sde_kms_setup_displays(struct drm_device *dev, max_encoders = sde_kms->dsi_display_count + sde_kms->wb_display_count + - sde_kms->hdmi_display_count; + sde_kms->hdmi_display_count + + sde_kms->shd_display_count; if (max_encoders > ARRAY_SIZE(priv->encoders)) { max_encoders = ARRAY_SIZE(priv->encoders); @@ -789,6 +822,48 @@ static int _sde_kms_setup_displays(struct drm_device *dev, } } + /* shd */ + for (i = 0; i < sde_kms->shd_display_count && + priv->num_encoders < max_encoders; ++i) { + display = sde_kms->shd_displays[i]; + encoder = NULL; + + memset(&info, 0x0, sizeof(info)); + rc = shd_connector_get_info(&info, display); + if (rc) { + SDE_ERROR("shd get_info %d failed\n", i); + continue; + } + + encoder = sde_encoder_init(dev, &info); + if (IS_ERR_OR_NULL(encoder)) { + SDE_ERROR("shd encoder init failed %d\n", i); + continue; + } + + rc = shd_drm_bridge_init(display, encoder); + if (rc) { + SDE_ERROR("shd bridge %d init failed, %d\n", i, rc); + sde_encoder_destroy(encoder); + continue; + } + + connector = sde_connector_init(dev, + encoder, + NULL, + display, + &shd_ops, + DRM_CONNECTOR_POLL_HPD, + info.intf_type); + if (connector) { + priv->encoders[priv->num_encoders++] = encoder; + priv->connectors[priv->num_connectors++] = connector; + } else { + SDE_ERROR("shd %d connector init failed\n", i); + shd_drm_bridge_deinit(display); + sde_encoder_destroy(encoder); + } + } return 0; } @@ -981,6 +1056,8 @@ static int sde_kms_postinit(struct msm_kms *kms) */ dev->vblank_disable_allowed = true; + shd_display_post_init(sde_kms); + return 0; } @@ -1094,6 +1171,13 @@ static void sde_kms_preclose(struct msm_kms *kms, struct drm_file *file) sde_crtc_cancel_pending_flip(priv->crtcs[i], file); } +static bool sde_kms_early_display_status(struct msm_kms *kms) +{ + struct sde_kms *sde_kms = to_sde_kms(kms); + + return sde_kms->splash_info.handoff; +} + static const struct msm_kms_funcs kms_funcs = { .hw_init = sde_kms_hw_init, .postinit = sde_kms_postinit, @@ -1113,6 +1197,7 @@ static const struct msm_kms_funcs kms_funcs = { .get_format = sde_get_msm_format, .round_pixclk = sde_kms_round_pixclk, .destroy = sde_kms_destroy, + .early_display_status = sde_kms_early_display_status, }; /* the caller api needs to turn on clock before calling it */ diff --git a/drivers/gpu/drm/msm/sde/sde_kms.h b/drivers/gpu/drm/msm/sde/sde_kms.h index c7355f5bd891..d3975e9b53e6 100644 --- a/drivers/gpu/drm/msm/sde/sde_kms.h +++ b/drivers/gpu/drm/msm/sde/sde_kms.h @@ -159,6 +159,8 @@ struct sde_kms { bool has_danger_ctrl; void **hdmi_displays; int hdmi_display_count; + int shd_display_count; + void **shd_displays; /* splash handoff structure */ struct sde_splash_info splash_info; diff --git a/drivers/gpu/drm/msm/sde/sde_plane.c b/drivers/gpu/drm/msm/sde/sde_plane.c index a35428c93867..91c837ff13dc 100644 --- a/drivers/gpu/drm/msm/sde/sde_plane.c +++ b/drivers/gpu/drm/msm/sde/sde_plane.c @@ -1232,6 +1232,7 @@ static int _sde_plane_mode_set(struct drm_plane *plane, uint32_t nplanes, src_flags = 0x0; struct sde_plane *psde; struct sde_plane_state *pstate; + struct sde_crtc_state *cstate; const struct sde_format *fmt; struct drm_crtc *crtc; struct drm_framebuffer *fb; @@ -1379,6 +1380,23 @@ static int _sde_plane_mode_set(struct drm_plane *plane, src.x += src.w * pp->index; dst.x += dst.w * pp->index; } + + /* add extra offset for shared display */ + if (crtc->state) { + cstate = to_sde_crtc_state(crtc->state); + if (cstate->is_shared) { + dst.x += cstate->shared_roi.x; + dst.y += cstate->shared_roi.y; + + if (sde_plane_get_property(pstate, + PLANE_PROP_SRC_CONFIG) & + BIT(SDE_DRM_LINEPADDING)) { + src.h = cstate->shared_roi.h; + dst.h = cstate->shared_roi.h; + } + } + } + pp->pipe_cfg.src_rect = src; pp->pipe_cfg.dst_rect = dst; @@ -1795,7 +1813,8 @@ static void _sde_plane_install_properties(struct drm_plane *plane, {SDE_DRM_BLEND_OP_COVERAGE, "coverage"} }; static const struct drm_prop_enum_list e_src_config[] = { - {SDE_DRM_DEINTERLACE, "deinterlace"} + {SDE_DRM_DEINTERLACE, "deinterlace"}, + {SDE_DRM_LINEPADDING, "linepadding"}, }; static const struct drm_prop_enum_list e_fb_translation_mode[] = { {SDE_DRM_FB_NON_SEC, "non_sec"}, diff --git a/drivers/gpu/drm/msm/sde/sde_rm.c b/drivers/gpu/drm/msm/sde/sde_rm.c index 384b6f50979c..4281d93fb182 100644 --- a/drivers/gpu/drm/msm/sde/sde_rm.c +++ b/drivers/gpu/drm/msm/sde/sde_rm.c @@ -36,6 +36,7 @@ #define RM_RQ_DSPP(r) ((r)->top_ctrl & BIT(SDE_RM_TOPCTL_DSPP)) #define RM_RQ_PPSPLIT(r) ((r)->top_ctrl & BIT(SDE_RM_TOPCTL_PPSPLIT)) #define RM_RQ_FORCE_TILING(r) ((r)->top_ctrl & BIT(SDE_RM_TOPCTL_FORCE_TILING)) +#define RM_RQ_FORCE_MIXER(r) ((r)->top_ctrl & BIT(SDE_RM_TOPCTL_FORCE_MIXER)) /** * struct sde_rm_requirements - Reservation requirements parameter bundle @@ -685,7 +686,7 @@ static int _sde_rm_reserve_lms( int i, rc = 0; if (!reqs->num_lm) { - SDE_ERROR("invalid number of lm: %d\n", reqs->num_lm); + SDE_DEBUG("invalid no of lm %d\n", reqs->num_lm); return -EINVAL; } @@ -1192,6 +1193,8 @@ static int _sde_rm_populate_requirements( reqs->top_ctrl = sde_connector_get_property(conn_state, CONNECTOR_PROP_TOPOLOGY_CONTROL); + SDE_DEBUG("%s reqs->top_ctrl = %llu\n", __func__, reqs->top_ctrl); + sde_encoder_get_hw_resources(enc, &reqs->hw_res, conn_state); /* Base assumption is LMs = h_tiles, conditions below may override */ @@ -1212,7 +1215,12 @@ static int _sde_rm_populate_requirements( } } else if (reqs->num_lm == 1) { - if (mode->hdisplay > rm->lm_max_width) { + if (RM_RQ_FORCE_MIXER(reqs)) { + /* user request serving wide display with 1 lm */ + reqs->top_name = SDE_RM_TOPOLOGY_SINGLEPIPE; + reqs->num_ctl = 1; + reqs->needs_split_display = false; + } else if (mode->hdisplay > rm->lm_max_width) { /* wide display, must split across 2 lm and merge */ reqs->top_name = SDE_RM_TOPOLOGY_DUALPIPEMERGE; reqs->num_lm = 2; @@ -1441,6 +1449,7 @@ int sde_rm_reserve( struct sde_rm_requirements reqs; struct msm_drm_private *priv; struct sde_kms *sde_kms; + struct sde_connector *sde_conn; int ret; if (!rm || !enc || !crtc_state || !conn_state) { @@ -1465,6 +1474,10 @@ int sde_rm_reserve( if (!drm_atomic_crtc_needs_modeset(crtc_state)) return 0; + sde_conn = to_sde_connector(conn_state->connector); + if (sde_conn->is_shared) + return 0; + SDE_DEBUG("reserving hw for conn %d enc %d crtc %d test_only %d\n", conn_state->connector->base.id, enc->base.id, crtc_state->crtc->base.id, test_only); @@ -1559,6 +1572,107 @@ end: return ret; } +int sde_rm_ext_blk_create_reserve(struct sde_rm *rm, + enum sde_hw_blk_type type, + uint32_t id, + void *hw, + struct drm_encoder *enc) +{ + struct sde_rm_hw_blk *blk; + struct sde_rm_rsvp *rsvp; + int ret = 0; + + if (!rm || !hw || !enc) { + SDE_ERROR("invalid parameters\n"); + return -EINVAL; + } + + if (type >= SDE_HW_BLK_MAX) { + SDE_ERROR("invalid HW type\n"); + return -EINVAL; + } + + blk = kzalloc(sizeof(*blk), GFP_KERNEL); + if (!blk) { + _sde_rm_hw_destroy(type, hw); + return -ENOMEM; + } + + mutex_lock(&rm->rm_lock); + + rsvp = _sde_rm_get_rsvp(rm, enc); + if (!rsvp) { + rsvp = kzalloc(sizeof(*rsvp), GFP_KERNEL); + if (!rsvp) { + ret = -ENOMEM; + kfree(blk); + goto end; + } + + rsvp->seq = ++rm->rsvp_next_seq; + rsvp->enc_id = enc->base.id; + list_add_tail(&rsvp->list, &rm->rsvps); + + SDE_DEBUG("create rsvp %d for enc %d\n", + rsvp->seq, rsvp->enc_id); + } + + blk->type = type; + blk->id = id; + blk->hw = hw; + blk->rsvp = rsvp; + list_add_tail(&blk->list, &rm->hw_blks[type]); + + SDE_DEBUG("create blk %d %d for rsvp %d enc %d\n", blk->type, blk->id, + rsvp->seq, rsvp->enc_id); + +end: + mutex_unlock(&rm->rm_lock); + return ret; +} + +int sde_rm_ext_blk_destroy(struct sde_rm *rm, + struct drm_encoder *enc) +{ + struct sde_rm_hw_blk *blk = NULL, *p; + struct sde_rm_rsvp *rsvp; + enum sde_hw_blk_type type; + int ret = 0; + + if (!rm || !enc) { + SDE_ERROR("invalid parameters\n"); + return -EINVAL; + } + + mutex_lock(&rm->rm_lock); + + rsvp = _sde_rm_get_rsvp(rm, enc); + if (!rsvp) { + ret = -ENOENT; + SDE_ERROR("failed to find rsvp for enc %d\n", enc->base.id); + goto end; + } + + for (type = 0; type < SDE_HW_BLK_MAX; type++) { + list_for_each_entry_safe(blk, p, &rm->hw_blks[type], list) { + if (blk->rsvp == rsvp) { + list_del(&blk->list); + SDE_DEBUG("del blk %d %d from rsvp %d enc %d\n", + blk->type, blk->id, + rsvp->seq, rsvp->enc_id); + kfree(blk); + } + } + } + + SDE_DEBUG("del rsvp %d\n", rsvp->seq); + list_del(&rsvp->list); + kfree(rsvp); +end: + mutex_unlock(&rm->rm_lock); + return ret; +} + static int _sde_rm_get_ctl_lm_for_splash(struct sde_hw_ctl *ctl, int max_lm_cnt, u8 lm_cnt, u8 *lm_ids, struct splash_ctl_top *top, int index) diff --git a/drivers/gpu/drm/msm/sde/sde_rm.h b/drivers/gpu/drm/msm/sde/sde_rm.h index bec398a3b996..d0a31f97d45b 100644 --- a/drivers/gpu/drm/msm/sde/sde_rm.h +++ b/drivers/gpu/drm/msm/sde/sde_rm.h @@ -52,6 +52,7 @@ enum sde_rm_topology_name { * of a single layer mixer. * @SDE_RM_TOPCTL_PPSPLIT: Require kernel to use pingpong split pipe * configuration instead of dual pipe. + * @SDE_RM_TOPCTL_FORCE_MIXER: Require kernel to force single mixer usage */ enum sde_rm_topology_control { SDE_RM_TOPCTL_RESERVE_LOCK, @@ -59,6 +60,7 @@ enum sde_rm_topology_control { SDE_RM_TOPCTL_DSPP, SDE_RM_TOPCTL_FORCE_TILING, SDE_RM_TOPCTL_PPSPLIT, + SDE_RM_TOPCTL_FORCE_MIXER, }; /** @@ -222,4 +224,30 @@ int sde_rm_read_resource_for_splash(struct sde_rm *rm, void *sinfo, struct sde_mdss_cfg *cat); +/** + * sde_rm_ext_blk_create_reserve - Create external HW blocks + * in resource manager and reserve for specific encoder. + * @rm: SDE Resource Manager handle + * @type: external HW block type + * @id: external HW block id + * @hw: external HW block + * @enc: DRM Encoder handle + * @Return: 0 on Success otherwise -ERROR + */ +int sde_rm_ext_blk_create_reserve(struct sde_rm *rm, + enum sde_hw_blk_type type, + uint32_t id, + void *hw, + struct drm_encoder *enc); + +/** + * sde_rm_ext_blk_destroy - Given the encoder for the display chain, release + * external HW blocks created for that. + * @rm: SDE Resource Manager handle + * @enc: DRM Encoder handle + * @Return: 0 on Success otherwise -ERROR + */ +int sde_rm_ext_blk_destroy(struct sde_rm *rm, + struct drm_encoder *enc); + #endif /* __SDE_RM_H__ */ diff --git a/drivers/gpu/drm/msm/sde/sde_shd.c b/drivers/gpu/drm/msm/sde/sde_shd.c new file mode 100644 index 000000000000..996aa9757a1f --- /dev/null +++ b/drivers/gpu/drm/msm/sde/sde_shd.c @@ -0,0 +1,1090 @@ +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + * + */ + +#define pr_fmt(fmt) "[drm-shd] %s: " fmt, __func__ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/debugfs.h> +#include <linux/component.h> +#include <linux/of_irq.h> +#include "sde_connector.h" +#include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic.h> +#include <drm/drm_crtc.h> + +#include "msm_drv.h" +#include "msm_kms.h" +#include "sde_connector.h" +#include "sde_encoder.h" +#include "sde_crtc.h" +#include "sde_shd.h" + +#define SHD_DEBUG(fmt, ...) pr_debug(fmt, ##__VA_ARGS__) + +static LIST_HEAD(g_base_list); + +static const struct of_device_id shd_dt_match[] = { + {.compatible = "qcom,shared-display"}, + {} +}; + +struct shd_bridge { + struct drm_bridge base; + struct shd_display *display; +}; + +int shd_display_get_num_of_displays(void) +{ + int display_num = 0; + struct shd_display *disp; + struct shd_display_base *base; + + list_for_each_entry(base, &g_base_list, head) { + list_for_each_entry(disp, &base->disp_list, head) + ++display_num; + } + + return display_num; +} + +int shd_display_get_displays(void **displays, int count) +{ + int display_num = 0; + struct shd_display *disp; + struct shd_display_base *base; + + list_for_each_entry(base, &g_base_list, head) + list_for_each_entry(disp, &base->disp_list, head) + displays[display_num++] = disp; + + return display_num; +} + +static enum drm_connector_status shd_display_base_detect( + struct drm_connector *connector, + bool force, + void *disp) +{ + return connector_status_disconnected; +} + +static int shd_display_init_base_connector(struct drm_device *dev, + struct shd_display_base *base) +{ + struct drm_encoder *encoder; + struct drm_connector *connector; + struct sde_connector *sde_conn; + int rc = 0; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + sde_conn = to_sde_connector(connector); + encoder = sde_conn->encoder; + if (encoder == base->encoder) { + base->connector = connector; + break; + } + } + + if (!base->connector) { + SDE_ERROR("failed to find connector\n"); + rc = -ENOENT; + goto error; + } + + /* set base connector disconnected*/ + sde_conn = to_sde_connector(base->connector); + base->ops = sde_conn->ops; + sde_conn->ops.detect = shd_display_base_detect; + + SHD_DEBUG("found base connector %d\n", base->connector->base.id); + +error: + return rc; +} + +static int shd_display_init_base_encoder(struct drm_device *dev, + struct shd_display_base *base) +{ + struct drm_encoder *encoder; + struct sde_encoder_hw_resources hw_res; + struct sde_connector_state conn_state = {}; + int i, rc = 0; + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + sde_encoder_get_hw_resources(encoder, + &hw_res, &conn_state.base); + for (i = 0; i < INTF_MAX; i++) { + if (hw_res.intfs[i] != INTF_MODE_NONE && + base->intf_idx == i) { + base->encoder = encoder; + goto found; + } + } + } + + if (!base->encoder) { + pr_err("can't find base encoder for intf %d\n", + base->intf_idx); + rc = -ENOENT; + goto error; + } + +found: + switch (base->encoder->encoder_type) { + case DRM_MODE_ENCODER_DSI: + base->connector_type = DRM_MODE_CONNECTOR_DSI; + break; + case DRM_MODE_ENCODER_TMDS: + base->connector_type = DRM_MODE_CONNECTOR_HDMIA; + break; + default: + base->connector_type = DRM_MODE_CONNECTOR_Unknown; + break; + } + + SHD_DEBUG("found base encoder %d, type %d, connect type %d\n", + base->encoder->base.id, + base->encoder->encoder_type, + base->connector_type); + +error: + return rc; +} + +static int shd_display_init_base_crtc(struct drm_device *dev, + struct shd_display_base *base) +{ + struct drm_crtc *crtc; + struct drm_display_mode *drm_mode; + int rc = 0; + + crtc = list_last_entry(&dev->mode_config.crtc_list, + struct drm_crtc, head); + + base->crtc = crtc; + base->encoder->crtc = crtc; + SHD_DEBUG("found base crtc %d\n", crtc->base.id); + + /* hide crtc from user */ + list_del_init(&crtc->head); + + /* fixed mode is used */ + drm_mode = &base->mode; + + /* update crtc drm structure */ + crtc->state->active = true; + rc = drm_atomic_set_mode_for_crtc(crtc->state, drm_mode); + if (rc) { + SDE_ERROR("Failed: set mode for crtc. rc = %d\n", rc); + goto error; + } + drm_mode_copy(&crtc->state->adjusted_mode, drm_mode); + drm_mode_copy(&crtc->mode, drm_mode); + + crtc->state->active_changed = true; + crtc->state->mode_changed = true; + crtc->state->connectors_changed = true; + + if (base->connector) { + base->connector->state->crtc = crtc; + base->connector->state->best_encoder = base->encoder; + base->connector->encoder = base->encoder; + } + +error: + return rc; +} + +static void shd_display_enable_base(struct drm_device *dev, + struct shd_display_base *base) +{ + const struct drm_encoder_helper_funcs *enc_funcs; + const struct drm_crtc_helper_funcs *crtc_funcs; + struct drm_display_mode *adjusted_mode; + struct sde_crtc *sde_crtc; + struct sde_hw_mixer_cfg lm_cfg; + struct sde_hw_mixer *hw_lm; + int rc, i; + + SHD_DEBUG("enable base display %d\n", base->intf_idx); + + enc_funcs = base->encoder->helper_private; + if (!enc_funcs) { + SDE_ERROR("failed to find encoder helper\n"); + return; + } + + crtc_funcs = base->crtc->helper_private; + if (!crtc_funcs) { + SDE_ERROR("failed to find crtc helper\n"); + return; + } + + if (!base->connector) { + SDE_ERROR("failed to find base connector\n"); + return; + } + + adjusted_mode = drm_mode_duplicate(dev, &base->mode); + if (!adjusted_mode) { + SDE_ERROR("failed to create adjusted mode\n"); + return; + } + + drm_bridge_mode_fixup(base->encoder->bridge, + &base->mode, + adjusted_mode); + + if (enc_funcs->atomic_check) { + rc = enc_funcs->atomic_check(base->encoder, + base->crtc->state, + base->connector->state); + if (rc) { + SDE_ERROR("encoder atomic check failed\n"); + goto state_fail; + } + } + + if (enc_funcs->mode_fixup) { + enc_funcs->mode_fixup(base->encoder, + &base->mode, + adjusted_mode); + } + + if (enc_funcs->mode_set) { + enc_funcs->mode_set(base->encoder, + &base->mode, + adjusted_mode); + } + + if (crtc_funcs->atomic_begin) { + crtc_funcs->atomic_begin(base->crtc, + base->crtc->state); + } + + sde_crtc = to_sde_crtc(base->crtc); + if (!sde_crtc->num_mixers) { + SDE_ERROR("no layer mixer found\n"); + goto state_fail; + } + + lm_cfg.out_width = base->mode.hdisplay / sde_crtc->num_mixers; + lm_cfg.out_height = base->mode.vdisplay; + lm_cfg.flags = 0; + for (i = 0; i < sde_crtc->num_mixers; i++) { + lm_cfg.right_mixer = i; + hw_lm = sde_crtc->mixers[i].hw_lm; + hw_lm->ops.setup_mixer_out(hw_lm, &lm_cfg); + } + + drm_bridge_mode_set(base->encoder->bridge, + &base->mode, + adjusted_mode); + + drm_bridge_pre_enable(base->encoder->bridge); + + if (enc_funcs->enable) + enc_funcs->enable(base->encoder); + + sde_encoder_kickoff(base->encoder); + + drm_bridge_enable(base->encoder->bridge); + + base->enabled = true; + +state_fail: + drm_mode_destroy(dev, adjusted_mode); +} + +static void shd_display_disable_base(struct drm_device *dev, + struct shd_display_base *base) +{ + const struct drm_encoder_helper_funcs *enc_funcs; + + SHD_DEBUG("disable base display %d\n", base->intf_idx); + + enc_funcs = base->encoder->helper_private; + if (!enc_funcs) { + SDE_ERROR("failed to find encoder helper\n"); + return; + } + + drm_bridge_disable(base->encoder->bridge); + + if (enc_funcs->disable) + enc_funcs->disable(base->encoder); + + drm_bridge_post_disable(base->encoder->bridge); + + base->enabled = false; +} + +static void shd_display_enable(struct shd_display *display) +{ + struct drm_device *dev = display->drm_dev; + struct shd_display_base *base = display->base; + + SHD_DEBUG("enable %s conn %d\n", display->name, + DRMID(display->connector)); + + mutex_lock(&base->base_mutex); + + display->enabled = true; + + if (!base->enabled) + shd_display_enable_base(dev, base); + + mutex_unlock(&base->base_mutex); +} + +static void shd_display_disable(struct shd_display *display) +{ + struct drm_device *dev = display->drm_dev; + struct shd_display_base *base = display->base; + struct shd_display *p; + bool enabled = false; + + SHD_DEBUG("disable %s conn %d\n", display->name, + DRMID(display->connector)); + + mutex_lock(&base->base_mutex); + + display->enabled = false; + + if (!base->enabled) + goto end; + + list_for_each_entry(p, &base->disp_list, head) { + if (p->enabled) { + enabled = true; + break; + } + } + + if (!enabled) + shd_display_disable_base(dev, base); + +end: + mutex_unlock(&base->base_mutex); +} + +void shd_display_prepare_commit(struct sde_kms *sde_kms, + struct drm_atomic_state *state) +{ + struct drm_connector *connector; + struct drm_connector_state *old_conn_state; + int i; + + if (!sde_kms->shd_display_count) + return; + + for_each_connector_in_state(state, connector, old_conn_state, i) { + struct sde_connector *sde_conn; + + sde_conn = to_sde_connector(connector); + if (!sde_conn->is_shared) + continue; + + if (!connector->state->best_encoder) + continue; + + if (!connector->state->crtc->state->active || + !drm_atomic_crtc_needs_modeset( + connector->state->crtc->state)) + continue; + + shd_display_enable(sde_conn->display); + } +} + +void shd_display_complete_commit(struct sde_kms *sde_kms, + struct drm_atomic_state *state) +{ + struct drm_connector *connector; + struct drm_connector_state *old_conn_state; + int i; + + if (!sde_kms->shd_display_count) + return; + + for_each_connector_in_state(state, connector, old_conn_state, i) { + struct sde_connector *sde_conn; + struct drm_crtc_state *old_crtc_state; + unsigned int crtc_idx; + + sde_conn = to_sde_connector(connector); + if (!sde_conn->is_shared) + continue; + + if (!old_conn_state->crtc) + continue; + + crtc_idx = drm_crtc_index(old_conn_state->crtc); + old_crtc_state = state->crtc_states[crtc_idx]; + + if (!old_crtc_state->active || + !drm_atomic_crtc_needs_modeset(old_conn_state->crtc->state)) + continue; + + if (old_conn_state->crtc->state->active) + continue; + + shd_display_disable(sde_conn->display); + } +} + +int shd_display_post_init(struct sde_kms *sde_kms) +{ + struct shd_display *disp; + struct shd_display_base *base; + int rc = 0, i; + + for (i = 0; i < sde_kms->shd_display_count; i++) { + disp = sde_kms->shd_displays[i]; + base = disp->base; + + if (base->crtc) + continue; + + rc = shd_display_init_base_crtc(disp->drm_dev, base); + if (rc) { + SDE_ERROR("failed initialize base crtc\n"); + break; + } + } + + return rc; +} + +int shd_connector_get_info(struct msm_display_info *info, void *data) +{ + struct shd_display *display = data; + int rc; + + if (!info || !data || !display->base) { + pr_err("invalid params\n"); + return -EINVAL; + } + + if (!display->base->encoder) { + rc = shd_display_init_base_encoder(display->drm_dev, + display->base); + if (rc) { + SDE_ERROR("failed to find base encoder\n"); + return rc; + } + + rc = shd_display_init_base_connector(display->drm_dev, + display->base); + if (rc) { + SDE_ERROR("failed to find base connector\n"); + return rc; + } + } + + info->intf_type = display->base->connector_type; + info->capabilities = MSM_DISPLAY_CAP_VID_MODE | + MSM_DISPLAY_CAP_HOT_PLUG; + info->is_connected = true; + info->num_of_h_tiles = 1; + info->h_tile_instance[0] = display->base->intf_idx; + info->capabilities |= MSM_DISPLAY_CAP_SHARED; + + return 0; +} + +int shd_connector_post_init(struct drm_connector *connector, + void *info, + void *display) +{ + struct shd_display *disp = display; + struct sde_connector *conn; + + disp->connector = connector; + conn = to_sde_connector(connector); + conn->is_shared = true; + conn->shared_roi = disp->roi; + + sde_kms_info_add_keyint(info, "max_blendstages", + disp->stage_range.size); + + sde_kms_info_add_keystr(info, "display type", + disp->display_type); + + if (disp->src.h != disp->roi.h) { + sde_kms_info_add_keyint(info, "padding height", + disp->roi.h); + } + + return 0; +} + +enum drm_connector_status shd_connector_detect(struct drm_connector *conn, + bool force, + void *display) +{ + struct shd_display *disp = display; + struct sde_connector *sde_conn; + enum drm_connector_status status = connector_status_disconnected; + + if (!conn || !display || !disp->base) { + pr_err("invalid params\n"); + goto end; + } + + mutex_lock(&disp->base->base_mutex); + if (disp->base->connector) { + sde_conn = to_sde_connector(disp->base->connector); + status = disp->base->ops.detect(disp->base->connector, + force, sde_conn->display); + } + mutex_unlock(&disp->base->base_mutex); + +end: + return status; +} + +int shd_connector_get_modes(struct drm_connector *connector, + void *display) +{ + struct drm_display_mode drm_mode; + struct shd_display *disp = display; + struct drm_display_mode *m; + + memcpy(&drm_mode, &disp->base->mode, sizeof(drm_mode)); + + drm_mode.hdisplay = disp->src.w; + drm_mode.hsync_start = drm_mode.hdisplay; + drm_mode.hsync_end = drm_mode.hsync_start; + drm_mode.htotal = drm_mode.hsync_end; + + drm_mode.vdisplay = disp->src.h; + drm_mode.vsync_start = drm_mode.vdisplay; + drm_mode.vsync_end = drm_mode.vsync_start; + drm_mode.vtotal = drm_mode.vsync_end; + + m = drm_mode_duplicate(disp->drm_dev, &drm_mode); + drm_mode_set_name(m); + drm_mode_probed_add(connector, m); + + return 1; +} + +enum drm_mode_status shd_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode, + void *display) +{ + return MODE_OK; +} + +static int shd_bridge_attach(struct drm_bridge *shd_bridge) +{ + return 0; +} + +static void shd_bridge_pre_enable(struct drm_bridge *drm_bridge) +{ +} + +static void shd_bridge_enable(struct drm_bridge *drm_bridge) +{ +} + +static void shd_bridge_disable(struct drm_bridge *drm_bridge) +{ +} + +static void shd_bridge_post_disable(struct drm_bridge *drm_bridge) +{ +} + + +static void shd_bridge_mode_set(struct drm_bridge *drm_bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ +} + +static bool shd_bridge_mode_fixup(struct drm_bridge *drm_bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static const struct drm_bridge_funcs shd_bridge_ops = { + .attach = shd_bridge_attach, + .mode_fixup = shd_bridge_mode_fixup, + .pre_enable = shd_bridge_pre_enable, + .enable = shd_bridge_enable, + .disable = shd_bridge_disable, + .post_disable = shd_bridge_post_disable, + .mode_set = shd_bridge_mode_set, +}; + +int shd_drm_bridge_init(void *data, struct drm_encoder *encoder) +{ + int rc = 0; + struct shd_bridge *bridge; + struct drm_device *dev; + struct shd_display *display = data; + struct msm_drm_private *priv = NULL; + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + rc = -ENOMEM; + goto error; + } + + dev = display->drm_dev; + bridge->display = display; + bridge->base.funcs = &shd_bridge_ops; + bridge->base.encoder = encoder; + + priv = dev->dev_private; + + rc = drm_bridge_attach(dev, &bridge->base); + if (rc) { + SDE_ERROR("failed to attach bridge, rc=%d\n", rc); + goto error_free_bridge; + } + + encoder->bridge = &bridge->base; + priv->bridges[priv->num_bridges++] = &bridge->base; + display->bridge = &bridge->base; + + return 0; + +error_free_bridge: + kfree(bridge); +error: + return rc; +} + +void shd_drm_bridge_deinit(void *data) +{ + struct shd_display *display = data; + struct shd_bridge *bridge = container_of(display->bridge, + struct shd_bridge, base); + + if (bridge && bridge->base.encoder) + bridge->base.encoder->bridge = NULL; + + kfree(bridge); +} + +/** + * sde_shd_bind - bind writeback device with controlling device + * @dev: Pointer to base of platform device + * @master: Pointer to container of drm device + * @data: Pointer to private data + * Returns: Zero on success + */ +static int sde_shd_bind(struct device *dev, struct device *master, void *data) +{ + struct shd_display *shd_dev; + + shd_dev = platform_get_drvdata(to_platform_device(dev)); + if (!shd_dev) { + SDE_ERROR("invalid shd device\n"); + return -EINVAL; + } + + shd_dev->drm_dev = dev_get_drvdata(master); + + return 0; +} + +/** + * sde_shd_unbind - unbind writeback from controlling device + * @dev: Pointer to base of platform device + * @master: Pointer to container of drm device + * @data: Pointer to private data + */ +static void sde_shd_unbind(struct device *dev, + struct device *master, void *data) +{ + struct shd_display *shd_dev; + + shd_dev = platform_get_drvdata(to_platform_device(dev)); + if (!shd_dev) { + SDE_ERROR("invalid shd device\n"); + return; + } + + shd_dev->drm_dev = NULL; +} + +static const struct component_ops sde_shd_comp_ops = { + .bind = sde_shd_bind, + .unbind = sde_shd_unbind, +}; + +static int sde_shd_parse_display(struct shd_display *display) +{ + struct device_node *of_node = display->pdev->dev.of_node; + struct device_node *of_src, *of_roi; + u32 src_w, src_h, dst_x, dst_y, dst_w, dst_h; + u32 range[2]; + int rc; + + display->name = of_node->full_name; + + display->display_type = of_get_property(of_node, + "qcom,display-type", NULL); + if (!display->display_type) + display->display_type = "unknown"; + + display->base_of = of_parse_phandle(of_node, + "qcom,shared-display-base", 0); + if (!display->base_of) { + pr_err("No base device present\n"); + rc = -ENODEV; + goto error; + } + + of_src = of_get_child_by_name(of_node, "qcom,shared-display-src-mode"); + if (!of_src) { + pr_err("No src mode present\n"); + rc = -ENODEV; + goto error; + } + + rc = of_property_read_u32(of_src, "qcom,mode-h-active", + &src_w); + if (rc) { + pr_err("Failed to parse h active\n"); + goto error; + } + + rc = of_property_read_u32(of_src, "qcom,mode-v-active", + &src_h); + if (rc) { + pr_err("Failed to parse v active\n"); + goto error; + } + + of_roi = of_get_child_by_name(of_node, "qcom,shared-display-dst-mode"); + if (!of_roi) { + pr_err("No roi mode present\n"); + rc = -ENODEV; + goto error; + } + + rc = of_property_read_u32(of_roi, "qcom,mode-x-offset", + &dst_x); + if (rc) { + pr_err("Failed to parse x offset\n"); + goto error; + } + + rc = of_property_read_u32(of_roi, "qcom,mode-y-offset", + &dst_y); + if (rc) { + pr_err("Failed to parse y offset\n"); + goto error; + } + + rc = of_property_read_u32(of_roi, "qcom,mode-width", + &dst_w); + if (rc) { + pr_err("Failed to parse roi width\n"); + goto error; + } + + rc = of_property_read_u32(of_roi, "qcom,mode-height", + &dst_h); + if (rc) { + pr_err("Failed to parse roi height\n"); + goto error; + } + + rc = of_property_read_u32_array(of_node, "qcom,blend-stage-range", + range, 2); + if (rc) + pr_err("Failed to parse blend stage range\n"); + + display->src.w = src_w; + display->src.h = src_h; + display->roi.x = dst_x; + display->roi.y = dst_y; + display->roi.w = dst_w; + display->roi.h = dst_h; + display->stage_range.start = range[0]; + display->stage_range.size = range[1]; + + SHD_DEBUG("%s src %dx%d dst %d,%d %dx%d range %d-%d\n", display->name, + display->src.w, display->src.h, + display->roi.x, display->roi.y, + display->roi.w, display->roi.h, + display->stage_range.start, + display->stage_range.size); + +error: + return rc; +} + +static int sde_shd_parse_base(struct shd_display_base *base) +{ + struct device_node *of_node = base->of_node; + struct device_node *node; + struct drm_display_mode *mode = &base->mode; + u32 h_front_porch, h_pulse_width, h_back_porch; + u32 v_front_porch, v_pulse_width, v_back_porch; + bool h_active_high, v_active_high; + u32 flags = 0; + int rc; + + rc = of_property_read_u32(of_node, "qcom,shared-display-base-intf", + &base->intf_idx); + if (rc) { + pr_err("failed to read base intf, rc=%d\n", rc); + goto fail; + } + + node = of_get_child_by_name(of_node, "qcom,shared-display-base-mode"); + if (!node) { + pr_err("No base mode present\n"); + rc = -ENODEV; + goto fail; + } + + rc = of_property_read_u32(node, "qcom,mode-h-active", + &mode->hdisplay); + if (rc) { + SDE_ERROR("failed to read h-active, rc=%d\n", rc); + goto fail; + } + + rc = of_property_read_u32(node, "qcom,mode-h-front-porch", + &h_front_porch); + if (rc) { + SDE_ERROR("failed to read h-front-porch, rc=%d\n", rc); + goto fail; + } + + rc = of_property_read_u32(node, "qcom,mode-h-pulse-width", + &h_pulse_width); + if (rc) { + SDE_ERROR("failed to read h-pulse-width, rc=%d\n", rc); + goto fail; + } + + rc = of_property_read_u32(node, "qcom,mode-h-back-porch", + &h_back_porch); + if (rc) { + SDE_ERROR("failed to read h-back-porch, rc=%d\n", rc); + goto fail; + } + + h_active_high = of_property_read_bool(node, + "qcom,mode-h-active-high"); + + rc = of_property_read_u32(node, "qcom,mode-v-active", + &mode->vdisplay); + if (rc) { + SDE_ERROR("failed to read v-active, rc=%d\n", rc); + goto fail; + } + + rc = of_property_read_u32(node, "qcom,mode-v-front-porch", + &v_front_porch); + if (rc) { + SDE_ERROR("failed to read v-front-porch, rc=%d\n", rc); + goto fail; + } + + rc = of_property_read_u32(node, "qcom,mode-v-pulse-width", + &v_pulse_width); + if (rc) { + SDE_ERROR("failed to read v-pulse-width, rc=%d\n", rc); + goto fail; + } + + rc = of_property_read_u32(node, "qcom,mode-v-back-porch", + &v_back_porch); + if (rc) { + SDE_ERROR("failed to read v-back-porch, rc=%d\n", rc); + goto fail; + } + + v_active_high = of_property_read_bool(node, + "qcom,mode-v-active-high"); + + rc = of_property_read_u32(node, "qcom,mode-refresh-rate", + &mode->vrefresh); + if (rc) { + SDE_ERROR("failed to read refresh-rate, rc=%d\n", rc); + goto fail; + } + + rc = of_property_read_u32(node, "qcom,mode-clock-in-khz", + &mode->clock); + if (rc) { + SDE_ERROR("failed to read clock, rc=%d\n", rc); + goto fail; + } + + mode->hsync_start = mode->hdisplay + h_front_porch; + mode->hsync_end = mode->hsync_start + h_pulse_width; + mode->htotal = mode->hsync_end + h_back_porch; + mode->vsync_start = mode->vdisplay + v_front_porch; + mode->vsync_end = mode->vsync_start + v_pulse_width; + mode->vtotal = mode->vsync_end + v_back_porch; + if (h_active_high) + flags |= DRM_MODE_FLAG_PHSYNC; + else + flags |= DRM_MODE_FLAG_NHSYNC; + if (v_active_high) + flags |= DRM_MODE_FLAG_PVSYNC; + else + flags |= DRM_MODE_FLAG_NVSYNC; + mode->flags = flags; + + SHD_DEBUG("base mode h[%d,%d,%d,%d] v[%d,%d,%d,%d] %d %xH %d\n", + mode->hdisplay, mode->hsync_start, + mode->hsync_end, mode->htotal, mode->vdisplay, + mode->vsync_start, mode->vsync_end, mode->vtotal, + mode->vrefresh, mode->flags, mode->clock); + +fail: + return rc; +} + +/** + * sde_shd_probe - load shared display module + * @pdev: Pointer to platform device + */ +static int sde_shd_probe(struct platform_device *pdev) +{ + struct shd_display *shd_dev; + struct shd_display_base *base; + int ret; + + shd_dev = devm_kzalloc(&pdev->dev, sizeof(*shd_dev), GFP_KERNEL); + if (!shd_dev) + return -ENOMEM; + + shd_dev->pdev = pdev; + + ret = sde_shd_parse_display(shd_dev); + if (ret) { + SDE_ERROR("failed to parse shared display\n"); + goto error; + } + + platform_set_drvdata(pdev, shd_dev); + + list_for_each_entry(base, &g_base_list, head) { + if (base->of_node == shd_dev->base_of) + goto next; + } + + base = devm_kzalloc(&pdev->dev, sizeof(*base), GFP_KERNEL); + if (!base) { + ret = -ENOMEM; + goto error; + } + + mutex_init(&base->base_mutex); + INIT_LIST_HEAD(&base->disp_list); + base->of_node = shd_dev->base_of; + + ret = sde_shd_parse_base(base); + if (ret) { + SDE_ERROR("failed to parse shared display base\n"); + goto base_error; + } + + list_add_tail(&base->head, &g_base_list); + +next: + shd_dev->base = base; + list_add_tail(&shd_dev->head, &base->disp_list); + SHD_DEBUG("add shd to intf %d\n", base->intf_idx); + + ret = component_add(&pdev->dev, &sde_shd_comp_ops); + if (ret) { + goto base_error; + pr_err("component add failed\n"); + } + + return 0; + +base_error: + devm_kfree(&pdev->dev, base); +error: + devm_kfree(&pdev->dev, shd_dev); + return ret; +} + +/** + * sde_shd_remove - unload shared display module + * @pdev: Pointer to platform device + */ +static int sde_shd_remove(struct platform_device *pdev) +{ + struct shd_display *shd_dev; + + shd_dev = platform_get_drvdata(pdev); + if (!shd_dev) + return 0; + + SHD_DEBUG("\n"); + + mutex_lock(&shd_dev->base->base_mutex); + list_del_init(&shd_dev->head); + if (list_empty(&shd_dev->base->disp_list)) { + list_del_init(&shd_dev->base->head); + mutex_unlock(&shd_dev->base->base_mutex); + devm_kfree(&pdev->dev, shd_dev->base); + } else + mutex_unlock(&shd_dev->base->base_mutex); + + platform_set_drvdata(pdev, NULL); + devm_kfree(&pdev->dev, shd_dev); + + return 0; +} + +static const struct of_device_id dt_match[] = { + { .compatible = "qcom,shared-display"}, + {} +}; + +static struct platform_driver sde_shd_driver = { + .probe = sde_shd_probe, + .remove = sde_shd_remove, + .driver = { + .name = "sde_shd", + .of_match_table = dt_match, + .suppress_bind_attrs = true, + }, +}; + +static int __init sde_shd_register(void) +{ + return platform_driver_register(&sde_shd_driver); +} + +static void __exit sde_shd_unregister(void) +{ + platform_driver_unregister(&sde_shd_driver); +} + +module_init(sde_shd_register); +module_exit(sde_shd_unregister); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/msm/sde/sde_shd.h b/drivers/gpu/drm/msm/sde/sde_shd.h new file mode 100644 index 000000000000..aedb3efb17ff --- /dev/null +++ b/drivers/gpu/drm/msm/sde/sde_shd.h @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + * + */ + +#ifndef _SDE_SHD_H_ +#define _SDE_SHD_H_ + +#include <linux/types.h> +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include "msm_drv.h" + +struct shd_mode_info { + int x_offset; + int y_offset; + int width; + int height; +}; + +struct shd_stage_range { + u32 start; + u32 size; +}; + +struct shd_display_base { + struct mutex base_mutex; + struct drm_display_mode mode; + struct drm_crtc *crtc; + struct drm_encoder *encoder; + struct drm_connector *connector; + struct list_head head; + struct list_head disp_list; + struct device_node *of_node; + struct sde_connector_ops ops; + + int intf_idx; + int connector_type; + bool enabled; +}; + +struct shd_display { + struct drm_device *drm_dev; + const char *name; + const char *display_type; + + struct shd_display_base *base; + struct drm_bridge *bridge; + struct drm_connector *connector; + + struct device_node *base_of; + struct sde_rect src; + struct sde_rect roi; + struct shd_stage_range stage_range; + + struct platform_device *pdev; + struct completion vsync_comp; + struct list_head head; + + bool enabled; +}; + +#ifdef CONFIG_DRM_SDE_SHD +int shd_display_get_num_of_displays(void); +int shd_display_get_displays(void **displays, int count); +int shd_display_post_init(struct sde_kms *sde_kms); +void shd_display_prepare_commit(struct sde_kms *sde_kms, + struct drm_atomic_state *state); +void shd_display_complete_commit(struct sde_kms *sde_kms, + struct drm_atomic_state *state); + +/** + * shd_connector_post_init - callback to perform additional initialization steps + * @connector: Pointer to drm connector structure + * @display: Pointer to private display handle + * Returns: Zero on success + */ +int shd_connector_post_init(struct drm_connector *connector, + void *info, + void *display); + +/** + * shd_connector_detect - callback to determine if connector is connected + * @connector: Pointer to drm connector structure + * @force: Force detect setting from drm framework + * @display: Pointer to private display handle + * Returns: Connector 'is connected' status + */ +enum drm_connector_status shd_connector_detect(struct drm_connector *conn, + bool force, + void *display); + +/** + * shd_connector_get_modes - callback to add drm modes via drm_mode_probed_add() + * @connector: Pointer to drm connector structure + * @display: Pointer to private display handle + * Returns: Number of modes added + */ +int shd_connector_get_modes(struct drm_connector *connector, + void *display); + +/** + * shd_connector_mode_valid - callback to determine if specified mode is valid + * @connector: Pointer to drm connector structure + * @mode: Pointer to drm mode structure + * @display: Pointer to private display handle + * Returns: Validity status for specified mode + */ +enum drm_mode_status shd_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode, + void *display); + +/** + * shd_connector_get_info - retrieve connector display info + * @connector: Pointer to drm connector structure + * @info: Out parameter. Information of the connected display + * @display: Pointer to private display structure + * Returns: zero on success + */ +int shd_connector_get_info(struct msm_display_info *info, void *display); + +/** + * shd_display_drm_bridge_init() - initializes DRM bridge object + * for shared display + * @display: Handle to the display. + * @encoder: Pointer to the encoder object which is connected to the + * display. + * Return: error code. + */ +int shd_drm_bridge_init(void *display, + struct drm_encoder *encoder); + +/** + * shd_display_drm_bridge_deinit() - destroys DRM bridge for the display + * @display: Handle to the display. + * Return: error code. + */ +void shd_drm_bridge_deinit(void *display); +#else +static inline +int shd_display_get_num_of_displays(void) +{ + return 0; +} + +static inline +int shd_display_get_displays(void **displays, int count) +{ + return 0; +} + +static inline +int shd_display_post_init(struct sde_kms *sde_kms) +{ + return 0; +} + +static inline +void shd_display_prepare_commit(struct sde_kms *sde_kms, + struct drm_atomic_state *state) +{ +} + +static inline +void shd_display_complete_commit(struct sde_kms *sde_kms, + struct drm_atomic_state *state) +{ +} + +static inline +int shd_connector_post_init(struct drm_connector *connector, + void *info, + void *display) +{ + return 0; +} + +static inline +enum drm_connector_status shd_connector_detect(struct drm_connector *conn, + bool force, + void *display) +{ + return connector_status_unknown; +} + +static inline +int shd_connector_get_modes(struct drm_connector *connector, + void *display) +{ + return 0; +} + +static inline +enum drm_mode_status shd_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode, + void *display) +{ + return MODE_ERROR; +} + +static inline +int shd_connector_get_info(struct msm_display_info *info, void *display) +{ + return -EINVAL; +} + +static inline +int shd_drm_bridge_init(void *display, + struct drm_encoder *encoder) +{ + return 0; +} + +static inline +void shd_drm_bridge_deinit(void *display) +{ +} +#endif + +#endif /* _SDE_SHD_H_ */ diff --git a/drivers/gpu/drm/msm/sde/sde_splash.c b/drivers/gpu/drm/msm/sde/sde_splash.c index ebb02dba6d5d..b0d63ec4c64c 100644 --- a/drivers/gpu/drm/msm/sde/sde_splash.c +++ b/drivers/gpu/drm/msm/sde/sde_splash.c @@ -15,6 +15,8 @@ #include <linux/debugfs.h> #include <linux/memblock.h> #include <soc/qcom/early_domain.h> +#include <linux/suspend.h> + #include "msm_drv.h" #include "msm_mmu.h" #include "sde_kms.h" @@ -40,6 +42,8 @@ #define SDE_LK_RUNNING_VALUE 0xC001CAFE #define SDE_LK_STOP_SPLASH_VALUE 0xDEADDEAD #define SDE_LK_EXIT_VALUE 0xDEADBEEF +#define SDE_LK_INTERMEDIATE_STOP 0xBEEFBEEF +#define SDE_LK_KERNEL_SPLASH_TALK_LOOP 20 #define INTF_HDMI_SEL (BIT(25) | BIT(24)) #define INTF_DSI0_SEL BIT(8) @@ -152,7 +156,35 @@ static bool _sde_splash_lk_check(void) */ static inline void _sde_splash_notify_lk_stop_splash(void) { + int i = 0; + int32_t *scratch_pad = NULL; + + /* request Lk to stop splash */ request_early_service_shutdown(EARLY_DISPLAY); + + /* + * Before next proceeding, kernel needs to check bootloader's + * intermediate status to ensure LK's concurrent flush is done. + */ + while (i++ < SDE_LK_KERNEL_SPLASH_TALK_LOOP) { + + scratch_pad = + (int32_t *)get_service_shared_mem_start(EARLY_DISPLAY); + + if (scratch_pad) { + if ((*scratch_pad != SDE_LK_INTERMEDIATE_STOP) && + (_sde_splash_lk_check())) { + DRM_INFO("wait for LK's intermediate ack\n"); + msleep(20); + } else { + SDE_DEBUG("received LK intermediate ack\n"); + break; + } + } + } + + if (i == SDE_LK_KERNEL_SPLASH_TALK_LOOP) + SDE_ERROR("Loop talk for LK and Kernel failed\n"); } static int _sde_splash_gem_new(struct drm_device *dev, @@ -729,7 +761,6 @@ bool sde_splash_get_lk_complete_status(struct msm_kms *kms) intr = sde_kms->hw_intr; if (sde_kms->splash_info.handoff && - !sde_kms->splash_info.display_splash_enabled && !_sde_splash_lk_check()) { SDE_DEBUG("LK totally exits\n"); return true; @@ -918,12 +949,20 @@ int sde_splash_lk_stop_splash(struct msm_kms *kms, mutex_lock(&sde_splash_lock); if (_sde_splash_validate_commit(sde_kms, state) && sinfo->display_splash_enabled) { - if (_sde_splash_lk_check()) + if (_sde_splash_lk_check()) { _sde_splash_notify_lk_stop_splash(); + error = _sde_splash_clear_mixer_blendstage(kms, state); + } - sinfo->display_splash_enabled = false; - - error = _sde_splash_clear_mixer_blendstage(kms, state); + if (get_hibernation_status() == true) { + sinfo->display_splash_enabled = false; + } else { + /* preserve the display_splash_enabled state for + * case when system is restoring from hibernation + * image and splash is enabled. + */ + sinfo->display_splash_enabled = true; + } } mutex_unlock(&sde_splash_lock); diff --git a/drivers/media/platform/msm/ais/ispif/msm_ispif.c b/drivers/media/platform/msm/ais/ispif/msm_ispif.c index 5ddf554d6ef3..ed220c82c11c 100644 --- a/drivers/media/platform/msm/ais/ispif/msm_ispif.c +++ b/drivers/media/platform/msm/ais/ispif/msm_ispif.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved. +/* Copyright (c) 2013-2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -215,32 +215,35 @@ static long msm_ispif_cmd_ext(struct v4l2_subdev *sd, long rc = 0; struct ispif_device *ispif = (struct ispif_device *)v4l2_get_subdevdata(sd); - struct ispif_cfg_data_ext pcdata; + struct ispif_cfg_data_ext pcdata = {0}; struct msm_ispif_param_data_ext *params = NULL; + + if (is_compat_task()) { #ifdef CONFIG_COMPAT - struct ispif_cfg_data_ext_32 *pcdata32 = - (struct ispif_cfg_data_ext_32 *)arg; + struct ispif_cfg_data_ext_32 *pcdata32 = + (struct ispif_cfg_data_ext_32 *)arg; - if (pcdata32 == NULL) { - pr_err("Invalid params passed from user\n"); - return -EINVAL; - } - pcdata.cfg_type = pcdata32->cfg_type; - pcdata.size = pcdata32->size; - pcdata.data = compat_ptr(pcdata32->data); + if (pcdata32 == NULL) { + pr_err("Invalid params passed from user\n"); + return -EINVAL; + } + pcdata.cfg_type = pcdata32->cfg_type; + pcdata.size = pcdata32->size; + pcdata.data = compat_ptr(pcdata32->data); -#else - struct ispif_cfg_data_ext *pcdata64 = - (struct ispif_cfg_data_ext *)arg; +#endif + } else { + struct ispif_cfg_data_ext *pcdata64 = + (struct ispif_cfg_data_ext *)arg; - if (pcdata64 == NULL) { - pr_err("Invalid params passed from user\n"); - return -EINVAL; + if (pcdata64 == NULL) { + pr_err("Invalid params passed from user\n"); + return -EINVAL; + } + pcdata.cfg_type = pcdata64->cfg_type; + pcdata.size = pcdata64->size; + pcdata.data = pcdata64->data; } - pcdata.cfg_type = pcdata64->cfg_type; - pcdata.size = pcdata64->size; - pcdata.data = pcdata64->data; -#endif if (pcdata.size != sizeof(struct msm_ispif_param_data_ext)) { pr_err("%s: payload size mismatch\n", __func__); return -EINVAL; diff --git a/drivers/media/platform/msm/camera_v2/common/Makefile b/drivers/media/platform/msm/camera_v2/common/Makefile index 74fe58f430e0..e3765fc4c524 100644 --- a/drivers/media/platform/msm/camera_v2/common/Makefile +++ b/drivers/media/platform/msm/camera_v2/common/Makefile @@ -1,3 +1,3 @@ ccflags-y += -Idrivers/media/platform/msm/camera_v2/ ccflags-y += -Idrivers/misc/ -obj-$(CONFIG_MSMB_CAMERA) += msm_camera_io_util.o cam_smmu_api.o cam_hw_ops.o cam_soc_api.o msm_camera_tz_util.o +obj-$(CONFIG_MSMB_CAMERA) += msm_camera_io_util.o cam_smmu_api.o cam_hw_ops.o cam_soc_api.o msm_camera_tz_util.o msm_cam_cx_ipeak.o diff --git a/drivers/media/platform/msm/camera_v2/common/msm_cam_cx_ipeak.c b/drivers/media/platform/msm/camera_v2/common/msm_cam_cx_ipeak.c new file mode 100644 index 000000000000..d9cea15eb666 --- /dev/null +++ b/drivers/media/platform/msm/camera_v2/common/msm_cam_cx_ipeak.c @@ -0,0 +1,89 @@ +/* Copyright (c) 2019, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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/of.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <soc/qcom/cx_ipeak.h> +#include "msm_cam_cx_ipeak.h" + +static struct cx_ipeak_client *cam_cx_ipeak; +static int cx_default_ipeak_mask; +static int cx_current_ipeak_mask; +static int cam_cx_client_cnt; + +int cam_cx_ipeak_register_cx_ipeak(struct cx_ipeak_client *p_cam_cx_ipeak, + int *cam_cx_ipeak_bit) +{ + int rc = 0; + + *cam_cx_ipeak_bit = 1 << cam_cx_client_cnt++; + cx_default_ipeak_mask |= *cam_cx_ipeak_bit; + + if (cam_cx_ipeak) + goto exit; + + cam_cx_ipeak = p_cam_cx_ipeak; + + if (!cam_cx_ipeak) + rc = -EINVAL; + +exit: + pr_debug("%s: client_cnt %d client mask = %x default_mask = %x\n", + __func__, cam_cx_client_cnt, *cam_cx_ipeak_bit, + cx_default_ipeak_mask); + return rc; +} + +int cam_cx_ipeak_update_vote_cx_ipeak(int cam_cx_ipeak_bit) +{ + int32_t soc_cx_ipeak_bit = cam_cx_ipeak_bit; + int ret = 0; + + pr_debug("%s: E: current_mask = %x default_mask = %x new bit = %x\n", + __func__, cx_current_ipeak_mask, + cx_default_ipeak_mask, soc_cx_ipeak_bit); + + cx_current_ipeak_mask |= soc_cx_ipeak_bit; + pr_debug("%s: current_mask = %x\n", __func__, cx_current_ipeak_mask); + + if (cx_current_ipeak_mask == cx_default_ipeak_mask) { + if (cam_cx_ipeak) { + ret = cx_ipeak_update(cam_cx_ipeak, true); + if (ret) + goto exit; + pr_debug("%s: X: All client VOTE\n", __func__); + } + } +exit: + return ret; +} + +int cam_cx_ipeak_unvote_cx_ipeak(int cam_cx_ipeak_bit) +{ + int32_t soc_cx_ipeak_bit = cam_cx_ipeak_bit; + + pr_debug("%s: current_mask = %x soc_cx_ipeak_bit = %x\n", __func__, + cx_current_ipeak_mask, soc_cx_ipeak_bit); + if (cx_current_ipeak_mask == cx_default_ipeak_mask) { + if (cam_cx_ipeak) + cx_ipeak_update(cam_cx_ipeak, false); + pr_debug("%s: One client requested UNVOTE\n", __func__); + } + cx_current_ipeak_mask &= (~soc_cx_ipeak_bit); + pr_debug("%s:X:updated cx_current_ipeak_mask = %x\n", __func__, + cx_current_ipeak_mask); + + return 0; +} diff --git a/drivers/media/platform/msm/camera_v2/common/msm_cam_cx_ipeak.h b/drivers/media/platform/msm/camera_v2/common/msm_cam_cx_ipeak.h new file mode 100644 index 000000000000..b60b896eb060 --- /dev/null +++ b/drivers/media/platform/msm/camera_v2/common/msm_cam_cx_ipeak.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2019, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#ifndef _MSM_CAM_CX_IPEAK_H_ +#define _MSM_CAM_CX_IPEAK_H_ + +#include <soc/qcom/cx_ipeak.h> + +int cam_cx_ipeak_register_cx_ipeak(struct cx_ipeak_client *p_cam_cx_ipeak, + int *cam_cx_ipeak_bit); + +int cam_cx_ipeak_update_vote_cx_ipeak(int cam_cx_ipeak_bit); +int cam_cx_ipeak_unvote_cx_ipeak(int cam_cx_ipeak_bit); + +#endif /* _CAM_CX_IPEAK_H_ */ diff --git a/drivers/media/platform/msm/camera_v2/ispif/msm_ispif.c b/drivers/media/platform/msm/camera_v2/ispif/msm_ispif.c index 9c3bd7b41ce9..da2381d24394 100644 --- a/drivers/media/platform/msm/camera_v2/ispif/msm_ispif.c +++ b/drivers/media/platform/msm/camera_v2/ispif/msm_ispif.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2013-2017, 2019 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -230,32 +230,34 @@ static long msm_ispif_cmd_ext(struct v4l2_subdev *sd, long rc = 0; struct ispif_device *ispif = (struct ispif_device *)v4l2_get_subdevdata(sd); - struct ispif_cfg_data_ext pcdata; + struct ispif_cfg_data_ext pcdata = {0}; struct msm_ispif_param_data_ext *params = NULL; -#ifdef CONFIG_COMPAT - struct ispif_cfg_data_ext_32 *pcdata32 = - (struct ispif_cfg_data_ext_32 *)arg; - if (pcdata32 == NULL) { - pr_err("Invalid params passed from user\n"); - return -EINVAL; - } - pcdata.cfg_type = pcdata32->cfg_type; - pcdata.size = pcdata32->size; - pcdata.data = compat_ptr(pcdata32->data); + if (is_compat_task()) { +#ifdef CONFIG_COMPAT + struct ispif_cfg_data_ext_32 *pcdata32 = + (struct ispif_cfg_data_ext_32 *)arg; -#else - struct ispif_cfg_data_ext *pcdata64 = + if (pcdata32 == NULL) { + pr_err("Invalid params passed from user\n"); + return -EINVAL; + } + pcdata.cfg_type = pcdata32->cfg_type; + pcdata.size = pcdata32->size; + pcdata.data = compat_ptr(pcdata32->data); +#endif + } else { + struct ispif_cfg_data_ext *pcdata64 = (struct ispif_cfg_data_ext *)arg; - if (pcdata64 == NULL) { - pr_err("Invalid params passed from user\n"); - return -EINVAL; + if (pcdata64 == NULL) { + pr_err("Invalid params passed from user\n"); + return -EINVAL; + } + pcdata.cfg_type = pcdata64->cfg_type; + pcdata.size = pcdata64->size; + pcdata.data = pcdata64->data; } - pcdata.cfg_type = pcdata64->cfg_type; - pcdata.size = pcdata64->size; - pcdata.data = pcdata64->data; -#endif if (pcdata.size != sizeof(struct msm_ispif_param_data_ext)) { pr_err("%s: payload size mismatch\n", __func__); return -EINVAL; diff --git a/drivers/media/platform/msm/vidc/msm_vdec.c b/drivers/media/platform/msm/vidc/msm_vdec.c index fc8da8dc0c2c..f838f99ab0c8 100644 --- a/drivers/media/platform/msm/vidc/msm_vdec.c +++ b/drivers/media/platform/msm/vidc/msm_vdec.c @@ -600,6 +600,16 @@ static struct msm_vidc_ctrl msm_vdec_ctrls[] = { .default_value = 0, .step = OPERATING_FRAME_RATE_STEP, }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_ALLOW_UBWC_LINEAR_EVENT, + .name = "Allow ubwc linear event", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = V4L2_MPEG_VIDC_VIDEO_ALLOW_UBWC_LINEAR_EVENT_DISABLE, + .maximum = V4L2_MPEG_VIDC_VIDEO_ALLOW_UBWC_LINEAR_EVENT_ENABLE, + .default_value = + V4L2_MPEG_VIDC_VIDEO_ALLOW_UBWC_LINEAR_EVENT_DISABLE, + .step = 1, + } }; #define NUM_CTRLS ARRAY_SIZE(msm_vdec_ctrls) @@ -2081,6 +2091,7 @@ int msm_vdec_inst_init(struct msm_vidc_inst *inst) inst->buffer_mode_set[CAPTURE_PORT] = HAL_BUFFER_MODE_STATIC; inst->prop.fps = DEFAULT_FPS; inst->operating_rate = 0; + inst->allow_ubwc_linear_event = 0; return rc; } @@ -2707,6 +2718,22 @@ static int try_set_ctrl(struct msm_vidc_inst *inst, struct v4l2_ctrl *ctrl) inst, inst->operating_rate >> 16, ctrl->val >> 16); inst->operating_rate = ctrl->val; break; + case V4L2_CID_MPEG_VIDC_VIDEO_ALLOW_UBWC_LINEAR_EVENT: + switch (ctrl->val) { + case V4L2_MPEG_VIDC_VIDEO_ALLOW_UBWC_LINEAR_EVENT_ENABLE: + inst->allow_ubwc_linear_event = 1; + break; + case V4L2_MPEG_VIDC_VIDEO_ALLOW_UBWC_LINEAR_EVENT_DISABLE: + inst->allow_ubwc_linear_event = 0; + break; + default: + dprintk(VIDC_ERR, + "Invalid allow ubwc linear event control value %d\n", + ctrl->val); + rc = -ENOTSUPP; + break; + } + break; default: break; } diff --git a/drivers/media/platform/msm/vidc/msm_vidc_common.c b/drivers/media/platform/msm/vidc/msm_vidc_common.c index 610ed9c6fed9..bbf5e33a99f7 100644 --- a/drivers/media/platform/msm/vidc/msm_vidc_common.c +++ b/drivers/media/platform/msm/vidc/msm_vidc_common.c @@ -1210,6 +1210,20 @@ static void handle_event_change(enum hal_command_response cmd, void *data) break; } + /* + * Force output to linear format if it's interlaced UBWC format + * to support interlaced clips playback + */ + if ((inst->allow_ubwc_linear_event) && + (event_notify->pic_struct == + MSM_VIDC_PIC_STRUCT_MAYBE_INTERLACED)) { + u32 fmt_fourcc = inst->fmts[CAPTURE_PORT].fourcc; + + if ((fmt_fourcc == V4L2_PIX_FMT_NV12_TP10_UBWC) || + (fmt_fourcc == V4L2_PIX_FMT_NV12_UBWC)) + inst->fmts[CAPTURE_PORT].fourcc = V4L2_PIX_FMT_NV12; + } + /* Bit depth and pic struct changed event are combined into a single * event (insufficient event) for the userspace. Currently bitdepth * changes is only for HEVC and interlaced support is for all diff --git a/drivers/media/platform/msm/vidc/msm_vidc_internal.h b/drivers/media/platform/msm/vidc/msm_vidc_internal.h index 08dad912bd57..95ec7e771d85 100644 --- a/drivers/media/platform/msm/vidc/msm_vidc_internal.h +++ b/drivers/media/platform/msm/vidc/msm_vidc_internal.h @@ -300,6 +300,7 @@ struct msm_vidc_inst { u32 pic_struct; u32 colour_space; u32 operating_rate; + bool allow_ubwc_linear_event; }; extern struct msm_vidc_drv *vidc_driver; diff --git a/drivers/net/wireless/cnss2/bus.c b/drivers/net/wireless/cnss2/bus.c index 4587d4ef162f..d8d8d5b75853 100644 --- a/drivers/net/wireless/cnss2/bus.c +++ b/drivers/net/wireless/cnss2/bus.c @@ -25,6 +25,8 @@ enum cnss_dev_bus_type cnss_get_dev_bus_type(struct device *dev) if (memcmp(dev->bus->name, "pci", 3) == 0) return CNSS_BUS_PCI; + else if (memcmp(dev->bus->name, "usb", 3) == 0) + return CNSS_BUS_USB; else return CNSS_BUS_NONE; } @@ -72,6 +74,8 @@ void *cnss_bus_dev_to_bus_priv(struct device *dev) switch (cnss_get_dev_bus_type(dev)) { case CNSS_BUS_PCI: return cnss_get_pci_priv(to_pci_dev(dev)); + case CNSS_BUS_USB: + return cnss_get_usb_priv(to_usb_interface(dev)); default: return NULL; } diff --git a/drivers/net/wireless/cnss2/main.c b/drivers/net/wireless/cnss2/main.c index 9c1b29fc6e27..249e3da75c98 100644 --- a/drivers/net/wireless/cnss2/main.c +++ b/drivers/net/wireless/cnss2/main.c @@ -259,6 +259,9 @@ int cnss_wlan_enable(struct device *dev, if (qmi_bypass) return 0; + if (cnss_get_bus_type(plat_priv->device_id) == CNSS_BUS_USB) + goto skip_cfg; + if (!config || !host_version) { cnss_pr_err("Invalid config or host_version pointer\n"); return -EINVAL; diff --git a/drivers/net/wireless/cnss2/pci.c b/drivers/net/wireless/cnss2/pci.c index 427b42c871f3..8816a12f654a 100644 --- a/drivers/net/wireless/cnss2/pci.c +++ b/drivers/net/wireless/cnss2/pci.c @@ -699,6 +699,7 @@ int cnss_pci_dev_ramdump(struct cnss_pci_data *pci_priv) break; case QCA6290_EMULATION_DEVICE_ID: case QCA6290_DEVICE_ID: + case QCN7605_DEVICE_ID: ret = cnss_qca6290_ramdump(pci_priv); break; default: diff --git a/drivers/net/wireless/cnss2/qmi.c b/drivers/net/wireless/cnss2/qmi.c index 85701566c58c..cd84f74a3d46 100644 --- a/drivers/net/wireless/cnss2/qmi.c +++ b/drivers/net/wireless/cnss2/qmi.c @@ -25,6 +25,7 @@ #define MAX_BDF_FILE_NAME 11 #define DEFAULT_BDF_FILE_NAME "bdwlan.elf" #define BDF_FILE_NAME_PREFIX "bdwlan.e" +#define DEFAULT_BIN_BDF_FILE_NAME "bdwlan.bin" #ifdef CONFIG_CNSS2_DEBUG static unsigned int qmi_timeout = 10000; @@ -510,6 +511,7 @@ int cnss_wlfw_bdf_dnld_send_sync(struct cnss_plat_data *plat_priv) const u8 *temp; unsigned int remaining; int ret = 0; + enum cnss_bdf_type bdf_type = CNSS_BDF_ELF; cnss_pr_dbg("Sending BDF download message, state: 0x%lx\n", plat_priv->driver_state); @@ -520,8 +522,18 @@ int cnss_wlfw_bdf_dnld_send_sync(struct cnss_plat_data *plat_priv) goto out; } + if (plat_priv->device_id == QCN7605_DEVICE_ID || + plat_priv->device_id == QCN7605_COMPOSITE_DEVICE_ID || + plat_priv->device_id == QCN7605_STANDALONE_DEVICE_ID) + bdf_type = CNSS_BDF_BIN; + if (plat_priv->board_info.board_id == 0xFF) - snprintf(filename, sizeof(filename), DEFAULT_BDF_FILE_NAME); + if (bdf_type == CNSS_BDF_BIN) + snprintf(filename, sizeof(filename), + DEFAULT_BIN_BDF_FILE_NAME); + else + snprintf(filename, sizeof(filename), + DEFAULT_BDF_FILE_NAME); else snprintf(filename, sizeof(filename), BDF_FILE_NAME_PREFIX "%02x", @@ -566,7 +578,7 @@ bypass_bdf: req->data_valid = 1; req->end_valid = 1; req->bdf_type_valid = 1; - req->bdf_type = CNSS_BDF_ELF; + req->bdf_type = bdf_type; if (remaining > QMI_WLFW_MAX_DATA_SIZE_V01) { req->data_len = QMI_WLFW_MAX_DATA_SIZE_V01; diff --git a/drivers/net/wireless/cnss2/usb.c b/drivers/net/wireless/cnss2/usb.c index 70aba70757cf..18915715edfd 100644 --- a/drivers/net/wireless/cnss2/usb.c +++ b/drivers/net/wireless/cnss2/usb.c @@ -71,6 +71,25 @@ void cnss_usb_wlan_unregister_driver(struct cnss_usb_wlan_driver *driver_ops) } EXPORT_SYMBOL(cnss_usb_wlan_unregister_driver); +int cnss_usb_is_device_down(struct device *dev) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + struct cnss_usb_data *usb_priv; + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL\n"); + return -ENODEV; + } + + usb_priv = plat_priv->bus_priv; + if (!usb_priv) { + cnss_pr_err("usb_priv is NULL\n"); + return -ENODEV; + } + return 0; +} +EXPORT_SYMBOL(cnss_usb_is_device_down); + int cnss_usb_register_driver_hdlr(struct cnss_usb_data *usb_priv, void *data) { @@ -262,10 +281,6 @@ static void cnss_usb_remove(struct usb_interface *interface) struct cnss_usb_data *usb_priv = plat_priv->bus_priv; cnss_pr_dbg("driver state %lu\n", plat_priv->driver_state); - if (usb_priv->driver_ops) { - cnss_pr_dbg("driver_op remove called for USB\n"); - usb_priv->driver_ops->remove(usb_priv->usb_intf); - } cnss_unregister_ramdump(plat_priv); cnss_unregister_subsys(plat_priv); usb_priv->plat_priv = NULL; diff --git a/drivers/net/wireless/cnss2/wlan_firmware_service_v01.c b/drivers/net/wireless/cnss2/wlan_firmware_service_v01.c index be66fd626095..51358bdc9303 100644 --- a/drivers/net/wireless/cnss2/wlan_firmware_service_v01.c +++ b/drivers/net/wireless/cnss2/wlan_firmware_service_v01.c @@ -2428,6 +2428,24 @@ struct elem_info wlfw_host_cap_req_msg_v01_ei[] = { mem_cfg_mode), }, { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x1D, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + cal_duration_valid), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(u16), + .is_array = NO_ARRAY, + .tlv_type = 0x1D, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + cal_duration), + }, + { .data_type = QMI_EOTI, .is_array = NO_ARRAY, .tlv_type = QMI_COMMON_TLV_TYPE, diff --git a/drivers/net/wireless/cnss2/wlan_firmware_service_v01.h b/drivers/net/wireless/cnss2/wlan_firmware_service_v01.h index c264373518b0..959c7d11b34a 100644 --- a/drivers/net/wireless/cnss2/wlan_firmware_service_v01.h +++ b/drivers/net/wireless/cnss2/wlan_firmware_service_v01.h @@ -625,9 +625,11 @@ struct wlfw_host_cap_req_msg_v01 { u32 mem_bucket; u8 mem_cfg_mode_valid; u8 mem_cfg_mode; + u8 cal_duration_valid; + u16 cal_duration; }; -#define WLFW_HOST_CAP_REQ_MSG_V01_MAX_MSG_LEN 189 +#define WLFW_HOST_CAP_REQ_MSG_V01_MAX_MSG_LEN 194 extern struct elem_info wlfw_host_cap_req_msg_v01_ei[]; struct wlfw_host_cap_resp_msg_v01 { diff --git a/drivers/soc/qcom/hab/hab_mem_linux.c b/drivers/soc/qcom/hab/hab_mem_linux.c index 670efdea212f..13bfab41f75d 100644 --- a/drivers/soc/qcom/hab/hab_mem_linux.c +++ b/drivers/soc/qcom/hab/hab_mem_linux.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -361,8 +361,7 @@ int habmem_hyp_grant_user(unsigned long address, ret = get_user_pages(current, current->mm, address, page_count, - 1, - 1, + FOLL_WRITE | FOLL_FORCE, pages, NULL); } diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index f3c389366873..b63ecb487aff 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -499,6 +499,7 @@ config USB_CONFIGFS_F_UAC2 depends on SND select USB_LIBCOMPOSITE select SND_PCM + select USB_U_AUDIO select USB_F_UAC2 help This Audio function is compatible with USB Audio Class diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index 1c9963ffbe35..fef3c11d84c3 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -44,18 +44,19 @@ struct f_hidg { /* configuration */ unsigned char bInterfaceSubClass; unsigned char bInterfaceProtocol; + unsigned char protocol; unsigned short report_desc_length; char *report_desc; unsigned short report_length; /* recv report */ struct list_head completed_out_req; - spinlock_t spinlock; + spinlock_t read_spinlock; wait_queue_head_t read_queue; unsigned int qlen; /* send report */ - struct mutex lock; + spinlock_t write_spinlock; bool write_pending; wait_queue_head_t write_queue; struct usb_request *req; @@ -98,6 +99,60 @@ static struct hid_descriptor hidg_desc = { /*.desc[0].wDescriptorLenght = DYNAMIC */ }; +/* Super-Speed Support */ + +static struct usb_endpoint_descriptor hidg_ss_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + /*.wMaxPacketSize = DYNAMIC */ + .bInterval = 4, /* FIXME: Add this field in the + * HID gadget configuration? + * (struct hidg_func_descriptor) + */ +}; + +static struct usb_ss_ep_comp_descriptor hidg_ss_in_comp_desc = { + .bLength = sizeof(hidg_ss_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + /* .wBytesPerInterval = DYNAMIC */ +}; + +static struct usb_endpoint_descriptor hidg_ss_out_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + /*.wMaxPacketSize = DYNAMIC */ + .bInterval = 4, /* FIXME: Add this field in the + * HID gadget configuration? + * (struct hidg_func_descriptor) + */ +}; + +static struct usb_ss_ep_comp_descriptor hidg_ss_out_comp_desc = { + .bLength = sizeof(hidg_ss_out_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + /* .wBytesPerInterval = DYNAMIC */ +}; + +static struct usb_descriptor_header *hidg_ss_descriptors[] = { + (struct usb_descriptor_header *)&hidg_interface_desc, + (struct usb_descriptor_header *)&hidg_desc, + (struct usb_descriptor_header *)&hidg_ss_in_ep_desc, + (struct usb_descriptor_header *)&hidg_ss_in_comp_desc, + (struct usb_descriptor_header *)&hidg_ss_out_ep_desc, + (struct usb_descriptor_header *)&hidg_ss_out_comp_desc, + NULL, +}; + /* High-Speed Support */ static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = { @@ -204,20 +259,20 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer, if (!access_ok(VERIFY_WRITE, buffer, count)) return -EFAULT; - spin_lock_irqsave(&hidg->spinlock, flags); + spin_lock_irqsave(&hidg->read_spinlock, flags); #define READ_COND (!list_empty(&hidg->completed_out_req)) /* wait for at least one buffer to complete */ while (!READ_COND) { - spin_unlock_irqrestore(&hidg->spinlock, flags); + spin_unlock_irqrestore(&hidg->read_spinlock, flags); if (file->f_flags & O_NONBLOCK) return -EAGAIN; if (wait_event_interruptible(hidg->read_queue, READ_COND)) return -ERESTARTSYS; - spin_lock_irqsave(&hidg->spinlock, flags); + spin_lock_irqsave(&hidg->read_spinlock, flags); } /* pick the first one */ @@ -232,7 +287,7 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer, req = list->req; count = min_t(unsigned int, count, req->actual - list->pos); - spin_unlock_irqrestore(&hidg->spinlock, flags); + spin_unlock_irqrestore(&hidg->read_spinlock, flags); /* copy to user outside spinlock */ count -= copy_to_user(buffer, req->buf + list->pos, count); @@ -254,9 +309,9 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer, return ret; } } else { - spin_lock_irqsave(&hidg->spinlock, flags); + spin_lock_irqsave(&hidg->read_spinlock, flags); list_add(&list->list, &hidg->completed_out_req); - spin_unlock_irqrestore(&hidg->spinlock, flags); + spin_unlock_irqrestore(&hidg->read_spinlock, flags); wake_up(&hidg->read_queue); } @@ -267,13 +322,16 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer, static void f_hidg_req_complete(struct usb_ep *ep, struct usb_request *req) { struct f_hidg *hidg = (struct f_hidg *)ep->driver_data; + unsigned long flags; if (req->status != 0) { ERROR(hidg->func.config->cdev, "End Point Request ERROR: %d\n", req->status); } + spin_lock_irqsave(&hidg->write_spinlock, flags); hidg->write_pending = 0; + spin_unlock_irqrestore(&hidg->write_spinlock, flags); wake_up(&hidg->write_queue); } @@ -281,18 +339,20 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer, size_t count, loff_t *offp) { struct f_hidg *hidg = file->private_data; + struct usb_request *req; + unsigned long flags; ssize_t status = -ENOMEM; if (!access_ok(VERIFY_READ, buffer, count)) return -EFAULT; - mutex_lock(&hidg->lock); + spin_lock_irqsave(&hidg->write_spinlock, flags); #define WRITE_COND (!hidg->write_pending) - +try_again: /* write queue */ while (!WRITE_COND) { - mutex_unlock(&hidg->lock); + spin_unlock_irqrestore(&hidg->write_spinlock, flags); if (file->f_flags & O_NONBLOCK) return -EAGAIN; @@ -300,37 +360,59 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer, hidg->write_queue, WRITE_COND)) return -ERESTARTSYS; - mutex_lock(&hidg->lock); + spin_lock_irqsave(&hidg->write_spinlock, flags); } + hidg->write_pending = 1; + req = hidg->req; count = min_t(unsigned, count, hidg->report_length); - status = copy_from_user(hidg->req->buf, buffer, count); + + spin_unlock_irqrestore(&hidg->write_spinlock, flags); + status = copy_from_user(req->buf, buffer, count); if (status != 0) { ERROR(hidg->func.config->cdev, "copy_from_user error\n"); - mutex_unlock(&hidg->lock); - return -EINVAL; + status = -EINVAL; + goto release_write_pending; } - hidg->req->status = 0; - hidg->req->zero = 0; - hidg->req->length = count; - hidg->req->complete = f_hidg_req_complete; - hidg->req->context = hidg; - hidg->write_pending = 1; + spin_lock_irqsave(&hidg->write_spinlock, flags); + + /* when our function has been disabled by host */ + if (!hidg->req) { + free_ep_req(hidg->in_ep, req); + /* + * TODO + * Should we fail with error here? + */ + goto try_again; + } - status = usb_ep_queue(hidg->in_ep, hidg->req, GFP_ATOMIC); + req->status = 0; + req->zero = 0; + req->length = count; + req->complete = f_hidg_req_complete; + req->context = hidg; + + status = usb_ep_queue(hidg->in_ep, req, GFP_ATOMIC); if (status < 0) { ERROR(hidg->func.config->cdev, "usb_ep_queue error on int endpoint %zd\n", status); - hidg->write_pending = 0; - wake_up(&hidg->write_queue); + goto release_write_pending_unlocked; } else { status = count; } + spin_unlock_irqrestore(&hidg->write_spinlock, flags); + + return status; +release_write_pending: + spin_lock_irqsave(&hidg->write_spinlock, flags); +release_write_pending_unlocked: + hidg->write_pending = 0; + spin_unlock_irqrestore(&hidg->write_spinlock, flags); - mutex_unlock(&hidg->lock); + wake_up(&hidg->write_queue); return status; } @@ -383,20 +465,36 @@ static inline struct usb_request *hidg_alloc_ep_req(struct usb_ep *ep, static void hidg_set_report_complete(struct usb_ep *ep, struct usb_request *req) { struct f_hidg *hidg = (struct f_hidg *) req->context; + struct usb_composite_dev *cdev = hidg->func.config->cdev; struct f_hidg_req_list *req_list; unsigned long flags; - req_list = kzalloc(sizeof(*req_list), GFP_ATOMIC); - if (!req_list) - return; + switch (req->status) { + case 0: + req_list = kzalloc(sizeof(*req_list), GFP_ATOMIC); + if (!req_list) { + ERROR(cdev, "Unable to allocate mem for req_list\n"); + goto free_req; + } - req_list->req = req; + req_list->req = req; - spin_lock_irqsave(&hidg->spinlock, flags); - list_add_tail(&req_list->list, &hidg->completed_out_req); - spin_unlock_irqrestore(&hidg->spinlock, flags); + spin_lock_irqsave(&hidg->read_spinlock, flags); + list_add_tail(&req_list->list, &hidg->completed_out_req); + spin_unlock_irqrestore(&hidg->read_spinlock, flags); - wake_up(&hidg->read_queue); + wake_up(&hidg->read_queue); + break; + default: + ERROR(cdev, "Set report failed %d\n", req->status); + /* FALLTHROUGH */ + case -ECONNABORTED: /* hardware forced ep reset */ + case -ECONNRESET: /* request dequeued */ + case -ESHUTDOWN: /* disconnect from host */ +free_req: + free_ep_req(ep, req); + return; + } } static int hidg_setup(struct usb_function *f, @@ -430,7 +528,9 @@ static int hidg_setup(struct usb_function *f, case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | HID_REQ_GET_PROTOCOL): VDBG(cdev, "get_protocol\n"); - goto stall; + length = min_t(unsigned int, length, 1); + ((u8 *) req->buf)[0] = hidg->protocol; + goto respond; break; case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 @@ -442,6 +542,17 @@ static int hidg_setup(struct usb_function *f, case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | HID_REQ_SET_PROTOCOL): VDBG(cdev, "set_protocol\n"); + if (value > HID_REPORT_PROTOCOL) + goto stall; + length = 0; + /* + * We assume that programs implementing the Boot protocol + * are also compatible with the Report Protocol + */ + if (hidg->bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) { + hidg->protocol = value; + goto respond; + } goto stall; break; @@ -507,19 +618,30 @@ static void hidg_disable(struct usb_function *f) usb_ep_disable(hidg->in_ep); usb_ep_disable(hidg->out_ep); - spin_lock_irqsave(&hidg->spinlock, flags); + spin_lock_irqsave(&hidg->read_spinlock, flags); list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) { free_ep_req(hidg->out_ep, list->req); list_del(&list->list); kfree(list); } - spin_unlock_irqrestore(&hidg->spinlock, flags); + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + + spin_lock_irqsave(&hidg->write_spinlock, flags); + if (!hidg->write_pending) { + free_ep_req(hidg->in_ep, hidg->req); + hidg->write_pending = 1; + } + + hidg->req = NULL; + spin_unlock_irqrestore(&hidg->write_spinlock, flags); } static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct usb_composite_dev *cdev = f->config->cdev; struct f_hidg *hidg = func_to_hidg(f); + struct usb_request *req_in = NULL; + unsigned long flags; int i, status = 0; VDBG(cdev, "hidg_set_alt intf:%d alt:%d\n", intf, alt); @@ -540,6 +662,12 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) goto fail; } hidg->in_ep->driver_data = hidg; + + req_in = hidg_alloc_ep_req(hidg->in_ep, hidg->report_length); + if (!req_in) { + status = -ENOMEM; + goto disable_ep_in; + } } @@ -551,12 +679,12 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) hidg->out_ep); if (status) { ERROR(cdev, "config_ep_by_speed FAILED!\n"); - goto fail; + goto free_req_in; } status = usb_ep_enable(hidg->out_ep); if (status < 0) { ERROR(cdev, "Enable OUT endpoint FAILED!\n"); - goto fail; + goto free_req_in; } hidg->out_ep->driver_data = hidg; @@ -572,17 +700,37 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) req->context = hidg; status = usb_ep_queue(hidg->out_ep, req, GFP_ATOMIC); - if (status) + if (status) { ERROR(cdev, "%s queue req --> %d\n", hidg->out_ep->name, status); + free_ep_req(hidg->out_ep, req); + } } else { - usb_ep_disable(hidg->out_ep); status = -ENOMEM; - goto fail; + goto disable_out_ep; } } } + if (hidg->in_ep != NULL) { + spin_lock_irqsave(&hidg->write_spinlock, flags); + hidg->req = req_in; + hidg->write_pending = 0; + spin_unlock_irqrestore(&hidg->write_spinlock, flags); + + wake_up(&hidg->write_queue); + } + return 0; +disable_out_ep: + usb_ep_disable(hidg->out_ep); +free_req_in: + if (req_in) + free_ep_req(hidg->in_ep, req_in); + +disable_ep_in: + if (hidg->in_ep) + usb_ep_disable(hidg->in_ep); + fail: return status; } @@ -631,21 +779,18 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) goto fail; hidg->out_ep = ep; - /* preallocate request and buffer */ - status = -ENOMEM; - hidg->req = usb_ep_alloc_request(hidg->in_ep, GFP_KERNEL); - if (!hidg->req) - goto fail; - - hidg->req->buf = kmalloc(hidg->report_length, GFP_KERNEL); - if (!hidg->req->buf) - goto fail; - /* set descriptor dynamic values */ hidg_interface_desc.bInterfaceSubClass = hidg->bInterfaceSubClass; hidg_interface_desc.bInterfaceProtocol = hidg->bInterfaceProtocol; + hidg->protocol = HID_REPORT_PROTOCOL; + hidg_ss_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + hidg_ss_in_comp_desc.wBytesPerInterval = + cpu_to_le16(hidg->report_length); hidg_hs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); hidg_fs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + hidg_ss_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + hidg_ss_out_comp_desc.wBytesPerInterval = + cpu_to_le16(hidg->report_length); hidg_hs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); hidg_fs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); /* @@ -661,13 +806,20 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) hidg_hs_out_ep_desc.bEndpointAddress = hidg_fs_out_ep_desc.bEndpointAddress; + hidg_ss_in_ep_desc.bEndpointAddress = + hidg_fs_in_ep_desc.bEndpointAddress; + hidg_ss_out_ep_desc.bEndpointAddress = + hidg_fs_out_ep_desc.bEndpointAddress; + status = usb_assign_descriptors(f, hidg_fs_descriptors, - hidg_hs_descriptors, NULL); + hidg_hs_descriptors, hidg_ss_descriptors); if (status) goto fail; - mutex_init(&hidg->lock); - spin_lock_init(&hidg->spinlock); + spin_lock_init(&hidg->write_spinlock); + hidg->write_pending = 1; + hidg->req = NULL; + spin_lock_init(&hidg->read_spinlock); init_waitqueue_head(&hidg->write_queue); init_waitqueue_head(&hidg->read_queue); INIT_LIST_HEAD(&hidg->completed_out_req); @@ -860,7 +1012,7 @@ static void hidg_free_inst(struct usb_function_instance *f) mutex_lock(&hidg_ida_lock); hidg_put_minor(opts->minor); - if (idr_is_empty(&hidg_ida.idr)) + if (ida_is_empty(&hidg_ida)) ghid_cleanup(); mutex_unlock(&hidg_ida_lock); @@ -886,7 +1038,7 @@ static struct usb_function_instance *hidg_alloc_inst(void) mutex_lock(&hidg_ida_lock); - if (idr_is_empty(&hidg_ida.idr)) { + if (ida_is_empty(&hidg_ida)) { status = ghid_setup(NULL, HIDG_MINORS); if (status) { ret = ERR_PTR(status); @@ -899,7 +1051,7 @@ static struct usb_function_instance *hidg_alloc_inst(void) if (opts->minor < 0) { ret = ERR_PTR(opts->minor); kfree(opts); - if (idr_is_empty(&hidg_ida.idr)) + if (ida_is_empty(&hidg_ida)) ghid_cleanup(); goto unlock; } @@ -931,10 +1083,6 @@ static void hidg_unbind(struct usb_configuration *c, struct usb_function *f) device_destroy(hidg_class, MKDEV(major, hidg->minor)); cdev_del(&hidg->cdev); - /* disable/free request and end point */ - usb_ep_disable(hidg->in_ep); - free_ep_req(hidg->in_ep, hidg->req); - usb_free_all_descriptors(f); } @@ -986,6 +1134,20 @@ static struct usb_function *hidg_alloc(struct usb_function_instance *fi) } DECLARE_USB_FUNCTION_INIT(hid, hidg_alloc_inst, hidg_alloc); + +static int __init afunc_init(void) +{ + return usb_function_register(&hidusb_func); +} + +static void __exit afunc_exit(void) +{ + usb_function_unregister(&hidusb_func); +} + +module_init(afunc_init); +module_exit(afunc_exit); + MODULE_LICENSE("GPL"); MODULE_AUTHOR("Fabien Chouteau"); diff --git a/drivers/usb/gadget/function/f_printer.c b/drivers/usb/gadget/function/f_printer.c index 0fbfb2b2aa08..d1793da0a066 100644 --- a/drivers/usb/gadget/function/f_printer.c +++ b/drivers/usb/gadget/function/f_printer.c @@ -1269,7 +1269,7 @@ static void gprinter_free_inst(struct usb_function_instance *f) mutex_lock(&printer_ida_lock); gprinter_put_minor(opts->minor); - if (idr_is_empty(&printer_ida.idr)) + if (ida_is_empty(&printer_ida)) gprinter_cleanup(); mutex_unlock(&printer_ida_lock); @@ -1293,7 +1293,7 @@ static struct usb_function_instance *gprinter_alloc_inst(void) mutex_lock(&printer_ida_lock); - if (idr_is_empty(&printer_ida.idr)) { + if (ida_is_empty(&printer_ida)) { status = gprinter_setup(PRINTER_MINORS); if (status) { ret = ERR_PTR(status); @@ -1306,7 +1306,7 @@ static struct usb_function_instance *gprinter_alloc_inst(void) if (opts->minor < 0) { ret = ERR_PTR(opts->minor); kfree(opts); - if (idr_is_empty(&printer_ida.idr)) + if (ida_is_empty(&printer_ida)) gprinter_cleanup(); goto unlock; } diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index e931c3cb0840..60f07ee86b4b 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -13,18 +13,11 @@ #include <linux/usb/audio.h> #include <linux/usb/audio-v2.h> -#include <linux/platform_device.h> #include <linux/module.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> - +#include "u_audio.h" #include "u_uac2.h" -/* Keep everyone on toes */ -#define USB_XFERS 2 - /* * The driver implements a simple UAC_2 topology. * USB-OUT -> IT_1 -> OT_3 -> ALSA_Capture @@ -54,504 +47,23 @@ #define UNFLW_CTRL 8 #define OVFLW_CTRL 10 -static const char *uac2_name = "snd_uac2"; - -struct uac2_req { - struct uac2_rtd_params *pp; /* parent param */ - struct usb_request *req; -}; - -struct uac2_rtd_params { - struct snd_uac2_chip *uac2; /* parent chip */ - bool ep_enabled; /* if the ep is enabled */ - /* Size of the ring buffer */ - size_t dma_bytes; - unsigned char *dma_area; - - struct snd_pcm_substream *ss; - - /* Ring buffer */ - ssize_t hw_ptr; - - void *rbuf; - - size_t period_size; - - unsigned max_psize; - struct uac2_req ureq[USB_XFERS]; - - spinlock_t lock; -}; - -struct snd_uac2_chip { - struct platform_device pdev; - struct platform_driver pdrv; - - struct uac2_rtd_params p_prm; - struct uac2_rtd_params c_prm; - - struct snd_card *card; - struct snd_pcm *pcm; - - /* timekeeping for the playback endpoint */ - unsigned int p_interval; - unsigned int p_residue; - - /* pre-calculated values for playback iso completion */ - unsigned int p_pktsize; - unsigned int p_pktsize_residue; - unsigned int p_framesize; +struct f_uac2 { + struct g_audio g_audio; + u8 ac_intf, as_in_intf, as_out_intf; + u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */ }; -#define BUFF_SIZE_MAX (PAGE_SIZE * 16) -#define PRD_SIZE_MAX PAGE_SIZE -#define MIN_PERIODS 4 - -static struct snd_pcm_hardware uac2_pcm_hardware = { - .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER - | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID - | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - .periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX, - .buffer_bytes_max = BUFF_SIZE_MAX, - .period_bytes_max = PRD_SIZE_MAX, - .periods_min = MIN_PERIODS, -}; - -struct audio_dev { - u8 ac_intf, ac_alt; - u8 as_out_intf, as_out_alt; - u8 as_in_intf, as_in_alt; - - struct usb_ep *in_ep, *out_ep; - struct usb_function func; - - /* The ALSA Sound Card it represents on the USB-Client side */ - struct snd_uac2_chip uac2; -}; - -static inline -struct audio_dev *func_to_agdev(struct usb_function *f) +static inline struct f_uac2 *func_to_uac2(struct usb_function *f) { - return container_of(f, struct audio_dev, func); + return container_of(f, struct f_uac2, g_audio.func); } static inline -struct audio_dev *uac2_to_agdev(struct snd_uac2_chip *u) -{ - return container_of(u, struct audio_dev, uac2); -} - -static inline -struct snd_uac2_chip *pdev_to_uac2(struct platform_device *p) -{ - return container_of(p, struct snd_uac2_chip, pdev); -} - -static inline -struct f_uac2_opts *agdev_to_uac2_opts(struct audio_dev *agdev) +struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev) { return container_of(agdev->func.fi, struct f_uac2_opts, func_inst); } -static inline -uint num_channels(uint chanmask) -{ - uint num = 0; - - while (chanmask) { - num += (chanmask & 1); - chanmask >>= 1; - } - - return num; -} - -static void -agdev_iso_complete(struct usb_ep *ep, struct usb_request *req) -{ - unsigned pending; - unsigned long flags; - unsigned int hw_ptr; - bool update_alsa = false; - int status = req->status; - struct uac2_req *ur = req->context; - struct snd_pcm_substream *substream; - struct uac2_rtd_params *prm = ur->pp; - struct snd_uac2_chip *uac2 = prm->uac2; - - /* i/f shutting down */ - if (!prm->ep_enabled || req->status == -ESHUTDOWN) - return; - - /* - * We can't really do much about bad xfers. - * Afterall, the ISOCH xfers could fail legitimately. - */ - if (status) - pr_debug("%s: iso_complete status(%d) %d/%d\n", - __func__, status, req->actual, req->length); - - substream = prm->ss; - - /* Do nothing if ALSA isn't active */ - if (!substream) - goto exit; - - spin_lock_irqsave(&prm->lock, flags); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - /* - * For each IN packet, take the quotient of the current data - * rate and the endpoint's interval as the base packet size. - * If there is a residue from this division, add it to the - * residue accumulator. - */ - req->length = uac2->p_pktsize; - uac2->p_residue += uac2->p_pktsize_residue; - - /* - * Whenever there are more bytes in the accumulator than we - * need to add one more sample frame, increase this packet's - * size and decrease the accumulator. - */ - if (uac2->p_residue / uac2->p_interval >= uac2->p_framesize) { - req->length += uac2->p_framesize; - uac2->p_residue -= uac2->p_framesize * - uac2->p_interval; - } - - req->actual = req->length; - } - - pending = prm->hw_ptr % prm->period_size; - pending += req->actual; - if (pending >= prm->period_size) - update_alsa = true; - - hw_ptr = prm->hw_ptr; - prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes; - - spin_unlock_irqrestore(&prm->lock, flags); - - /* Pack USB load in ALSA ring buffer */ - pending = prm->dma_bytes - hw_ptr; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - if (unlikely(pending < req->actual)) { - memcpy(req->buf, prm->dma_area + hw_ptr, pending); - memcpy(req->buf + pending, prm->dma_area, - req->actual - pending); - } else { - memcpy(req->buf, prm->dma_area + hw_ptr, req->actual); - } - } else { - if (unlikely(pending < req->actual)) { - memcpy(prm->dma_area + hw_ptr, req->buf, pending); - memcpy(prm->dma_area, req->buf + pending, - req->actual - pending); - } else { - memcpy(prm->dma_area + hw_ptr, req->buf, req->actual); - } - } - -exit: - if (usb_ep_queue(ep, req, GFP_ATOMIC)) - dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__); - - if (update_alsa) - snd_pcm_period_elapsed(substream); - - return; -} - -static int -uac2_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct uac2_rtd_params *prm; - unsigned long flags; - int err = 0; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - prm = &uac2->p_prm; - else - prm = &uac2->c_prm; - - spin_lock_irqsave(&prm->lock, flags); - - /* Reset */ - prm->hw_ptr = 0; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - prm->ss = substream; - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - prm->ss = NULL; - break; - default: - err = -EINVAL; - } - - spin_unlock_irqrestore(&prm->lock, flags); - - /* Clear buffer after Play stops */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss) - memset(prm->rbuf, 0, prm->max_psize * USB_XFERS); - - return err; -} - -static snd_pcm_uframes_t uac2_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct uac2_rtd_params *prm; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - prm = &uac2->p_prm; - else - prm = &uac2->c_prm; - - return bytes_to_frames(substream->runtime, prm->hw_ptr); -} - -static int uac2_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct uac2_rtd_params *prm; - int err; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - prm = &uac2->p_prm; - else - prm = &uac2->c_prm; - - err = snd_pcm_lib_malloc_pages(substream, - params_buffer_bytes(hw_params)); - if (err >= 0) { - prm->dma_bytes = substream->runtime->dma_bytes; - prm->dma_area = substream->runtime->dma_area; - prm->period_size = params_period_bytes(hw_params); - } - - return err; -} - -static int uac2_pcm_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct uac2_rtd_params *prm; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - prm = &uac2->p_prm; - else - prm = &uac2->c_prm; - - prm->dma_area = NULL; - prm->dma_bytes = 0; - prm->period_size = 0; - - return snd_pcm_lib_free_pages(substream); -} - -static int uac2_pcm_open(struct snd_pcm_substream *substream) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; - struct audio_dev *audio_dev; - struct f_uac2_opts *opts; - int p_ssize, c_ssize; - int p_srate, c_srate; - int p_chmask, c_chmask; - - audio_dev = uac2_to_agdev(uac2); - opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst); - p_ssize = opts->p_ssize; - c_ssize = opts->c_ssize; - p_srate = opts->p_srate; - c_srate = opts->c_srate; - p_chmask = opts->p_chmask; - c_chmask = opts->c_chmask; - uac2->p_residue = 0; - - runtime->hw = uac2_pcm_hardware; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - spin_lock_init(&uac2->p_prm.lock); - runtime->hw.rate_min = p_srate; - switch (p_ssize) { - case 3: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; - break; - case 4: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; - break; - default: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; - break; - } - runtime->hw.channels_min = num_channels(p_chmask); - runtime->hw.period_bytes_min = 2 * uac2->p_prm.max_psize - / runtime->hw.periods_min; - } else { - spin_lock_init(&uac2->c_prm.lock); - runtime->hw.rate_min = c_srate; - switch (c_ssize) { - case 3: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; - break; - case 4: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; - break; - default: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; - break; - } - runtime->hw.channels_min = num_channels(c_chmask); - runtime->hw.period_bytes_min = 2 * uac2->c_prm.max_psize - / runtime->hw.periods_min; - } - - runtime->hw.rate_max = runtime->hw.rate_min; - runtime->hw.channels_max = runtime->hw.channels_min; - - snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - - return 0; -} - -/* ALSA cries without these function pointers */ -static int uac2_pcm_null(struct snd_pcm_substream *substream) -{ - return 0; -} - -static struct snd_pcm_ops uac2_pcm_ops = { - .open = uac2_pcm_open, - .close = uac2_pcm_null, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = uac2_pcm_hw_params, - .hw_free = uac2_pcm_hw_free, - .trigger = uac2_pcm_trigger, - .pointer = uac2_pcm_pointer, - .prepare = uac2_pcm_null, -}; - -static int snd_uac2_probe(struct platform_device *pdev) -{ - struct snd_uac2_chip *uac2 = pdev_to_uac2(pdev); - struct snd_card *card; - struct snd_pcm *pcm; - struct audio_dev *audio_dev; - struct f_uac2_opts *opts; - int err; - int p_chmask, c_chmask; - - audio_dev = uac2_to_agdev(uac2); - opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst); - p_chmask = opts->p_chmask; - c_chmask = opts->c_chmask; - - /* Choose any slot, with no id */ - err = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, 0, &card); - if (err < 0) - return err; - - uac2->card = card; - - /* - * Create first PCM device - * Create a substream only for non-zero channel streams - */ - err = snd_pcm_new(uac2->card, "UAC2 PCM", 0, - p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm); - if (err < 0) - goto snd_fail; - - strcpy(pcm->name, "UAC2 PCM"); - pcm->private_data = uac2; - - uac2->pcm = pcm; - - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac2_pcm_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac2_pcm_ops); - - strcpy(card->driver, "UAC2_Gadget"); - strcpy(card->shortname, "UAC2_Gadget"); - sprintf(card->longname, "UAC2_Gadget %i", pdev->id); - - snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX); - - err = snd_card_register(card); - if (!err) { - platform_set_drvdata(pdev, card); - return 0; - } - -snd_fail: - snd_card_free(card); - - uac2->pcm = NULL; - uac2->card = NULL; - - return err; -} - -static int snd_uac2_remove(struct platform_device *pdev) -{ - struct snd_card *card = platform_get_drvdata(pdev); - - if (card) - return snd_card_free(card); - - return 0; -} - -static void snd_uac2_release(struct device *dev) -{ - dev_dbg(dev, "releasing '%s'\n", dev_name(dev)); -} - -static int alsa_uac2_init(struct audio_dev *agdev) -{ - struct snd_uac2_chip *uac2 = &agdev->uac2; - int err; - - uac2->pdrv.probe = snd_uac2_probe; - uac2->pdrv.remove = snd_uac2_remove; - uac2->pdrv.driver.name = uac2_name; - - uac2->pdev.id = 0; - uac2->pdev.name = uac2_name; - uac2->pdev.dev.release = snd_uac2_release; - - /* Register snd_uac2 driver */ - err = platform_driver_register(&uac2->pdrv); - if (err) - return err; - - /* Register snd_uac2 device */ - err = platform_device_register(&uac2->pdev); - if (err) - platform_driver_unregister(&uac2->pdrv); - - return err; -} - -static void alsa_uac2_exit(struct audio_dev *agdev) -{ - struct snd_uac2_chip *uac2 = &agdev->uac2; - - platform_driver_unregister(&uac2->pdrv); - platform_device_unregister(&uac2->pdev); -} - - /* --------- USB Function Interface ------------- */ enum { @@ -598,18 +110,6 @@ static struct usb_gadget_strings *fn_strings[] = { NULL, }; -static struct usb_qualifier_descriptor devqual_desc = { - .bLength = sizeof devqual_desc, - .bDescriptorType = USB_DT_DEVICE_QUALIFIER, - - .bcdUSB = cpu_to_le16(0x200), - .bDeviceClass = USB_CLASS_MISC, - .bDeviceSubClass = 0x02, - .bDeviceProtocol = 0x01, - .bNumConfigurations = 1, - .bRESERVED = 0, -}; - static struct usb_interface_assoc_descriptor iad_desc = { .bLength = sizeof iad_desc, .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, @@ -951,30 +451,6 @@ struct cntrl_range_lay3 { __le32 dRES; } __packed; -static inline void -free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep) -{ - struct snd_uac2_chip *uac2 = prm->uac2; - int i; - - if (!prm->ep_enabled) - return; - - prm->ep_enabled = false; - - for (i = 0; i < USB_XFERS; i++) { - if (prm->ureq[i].req) { - usb_ep_dequeue(ep, prm->ureq[i].req); - usb_ep_free_request(ep, prm->ureq[i].req); - prm->ureq[i].req = NULL; - } - } - - if (usb_ep_disable(ep)) - dev_err(&uac2->pdev.dev, - "%s:%d Error!\n", __func__, __LINE__); -} - static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, struct usb_endpoint_descriptor *ep_desc, unsigned int factor, bool is_playback) @@ -1001,12 +477,11 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, static int afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) { - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); struct usb_composite_dev *cdev = cfg->cdev; struct usb_gadget *gadget = cdev->gadget; - struct device *dev = &uac2->pdev.dev; - struct uac2_rtd_params *prm; + struct device *dev = &gadget->dev; struct f_uac2_opts *uac2_opts; struct usb_string *us; int ret; @@ -1055,8 +530,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) iad_desc.bFirstInterface = ret; std_ac_if_desc.bInterfaceNumber = ret; - agdev->ac_intf = ret; - agdev->ac_alt = 0; + uac2->ac_intf = ret; + uac2->ac_alt = 0; ret = usb_interface_id(cfg, fn); if (ret < 0) { @@ -1065,8 +540,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) } std_as_out_if0_desc.bInterfaceNumber = ret; std_as_out_if1_desc.bInterfaceNumber = ret; - agdev->as_out_intf = ret; - agdev->as_out_alt = 0; + uac2->as_out_intf = ret; + uac2->as_out_alt = 0; ret = usb_interface_id(cfg, fn); if (ret < 0) { @@ -1075,8 +550,14 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) } std_as_in_if0_desc.bInterfaceNumber = ret; std_as_in_if1_desc.bInterfaceNumber = ret; - agdev->as_in_intf = ret; - agdev->as_in_alt = 0; + uac2->as_in_intf = ret; + uac2->as_in_alt = 0; + + /* Calculate wMaxPacketSize according to audio bandwidth */ + set_ep_max_packet_size(uac2_opts, &fs_epin_desc, 1000, true); + set_ep_max_packet_size(uac2_opts, &fs_epout_desc, 1000, false); + set_ep_max_packet_size(uac2_opts, &hs_epin_desc, 8000, true); + set_ep_max_packet_size(uac2_opts, &hs_epout_desc, 8000, false); agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc); if (!agdev->out_ep) { @@ -1090,14 +571,10 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) return ret; } - uac2->p_prm.uac2 = uac2; - uac2->c_prm.uac2 = uac2; - - /* Calculate wMaxPacketSize according to audio bandwidth */ - set_ep_max_packet_size(uac2_opts, &fs_epin_desc, 1000, true); - set_ep_max_packet_size(uac2_opts, &fs_epout_desc, 1000, false); - set_ep_max_packet_size(uac2_opts, &hs_epin_desc, 8000, true); - set_ep_max_packet_size(uac2_opts, &hs_epout_desc, 8000, false); + agdev->in_ep_maxpsize = max(fs_epin_desc.wMaxPacketSize, + hs_epin_desc.wMaxPacketSize); + agdev->out_ep_maxpsize = max(fs_epout_desc.wMaxPacketSize, + hs_epout_desc.wMaxPacketSize); hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; @@ -1106,47 +583,34 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) if (ret) return ret; - prm = &agdev->uac2.c_prm; - prm->max_psize = hs_epout_desc.wMaxPacketSize; - prm->rbuf = kzalloc(prm->max_psize * USB_XFERS, GFP_KERNEL); - if (!prm->rbuf) { - prm->max_psize = 0; - goto err_free_descs; - } - - prm = &agdev->uac2.p_prm; - prm->max_psize = hs_epin_desc.wMaxPacketSize; - prm->rbuf = kzalloc(prm->max_psize * USB_XFERS, GFP_KERNEL); - if (!prm->rbuf) { - prm->max_psize = 0; - goto err; - } + agdev->gadget = gadget; - ret = alsa_uac2_init(agdev); + agdev->params.p_chmask = uac2_opts->p_chmask; + agdev->params.p_srate = uac2_opts->p_srate; + agdev->params.p_ssize = uac2_opts->p_ssize; + agdev->params.c_chmask = uac2_opts->c_chmask; + agdev->params.c_srate = uac2_opts->c_srate; + agdev->params.c_ssize = uac2_opts->c_ssize; + agdev->params.req_number = uac2_opts->req_number; + ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget"); if (ret) - goto err; + goto err_free_descs; return 0; -err: - kfree(agdev->uac2.p_prm.rbuf); - kfree(agdev->uac2.c_prm.rbuf); err_free_descs: usb_free_all_descriptors(fn); - return -EINVAL; + agdev->gadget = NULL; + return ret; } static int afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) { struct usb_composite_dev *cdev = fn->config->cdev; - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct f_uac2 *uac2 = func_to_uac2(fn); struct usb_gadget *gadget = cdev->gadget; - struct device *dev = &uac2->pdev.dev; - struct usb_request *req; - struct usb_ep *ep; - struct uac2_rtd_params *prm; - int req_len, i; + struct device *dev = &gadget->dev; + int ret = 0; /* No i/f has more than 2 alt settings */ if (alt > 1) { @@ -1154,7 +618,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) return -EINVAL; } - if (intf == agdev->ac_intf) { + if (intf == uac2->ac_intf) { /* Control I/f has only 1 AltSetting - 0 */ if (alt) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); @@ -1163,96 +627,42 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) return 0; } - if (intf == agdev->as_out_intf) { - ep = agdev->out_ep; - prm = &uac2->c_prm; - config_ep_by_speed(gadget, fn, ep); - agdev->as_out_alt = alt; - req_len = prm->max_psize; - } else if (intf == agdev->as_in_intf) { - struct f_uac2_opts *opts = agdev_to_uac2_opts(agdev); - unsigned int factor, rate; - struct usb_endpoint_descriptor *ep_desc; - - ep = agdev->in_ep; - prm = &uac2->p_prm; - config_ep_by_speed(gadget, fn, ep); - agdev->as_in_alt = alt; - - /* pre-calculate the playback endpoint's interval */ - if (gadget->speed == USB_SPEED_FULL) { - ep_desc = &fs_epin_desc; - factor = 1000; - } else { - ep_desc = &hs_epin_desc; - factor = 8000; - } - - /* pre-compute some values for iso_complete() */ - uac2->p_framesize = opts->p_ssize * - num_channels(opts->p_chmask); - rate = opts->p_srate * uac2->p_framesize; - uac2->p_interval = factor / (1 << (ep_desc->bInterval - 1)); - uac2->p_pktsize = min_t(unsigned int, rate / uac2->p_interval, - prm->max_psize); + if (intf == uac2->as_out_intf) { + uac2->as_out_alt = alt; - if (uac2->p_pktsize < prm->max_psize) - uac2->p_pktsize_residue = rate % uac2->p_interval; + if (alt) + ret = u_audio_start_capture(&uac2->g_audio); else - uac2->p_pktsize_residue = 0; + u_audio_stop_capture(&uac2->g_audio); + } else if (intf == uac2->as_in_intf) { + uac2->as_in_alt = alt; - req_len = uac2->p_pktsize; - uac2->p_residue = 0; + if (alt) + ret = u_audio_start_playback(&uac2->g_audio); + else + u_audio_stop_playback(&uac2->g_audio); } else { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return -EINVAL; } - if (alt == 0) { - free_ep(prm, ep); - return 0; - } - - prm->ep_enabled = true; - usb_ep_enable(ep); - - for (i = 0; i < USB_XFERS; i++) { - if (!prm->ureq[i].req) { - req = usb_ep_alloc_request(ep, GFP_ATOMIC); - if (req == NULL) - return -ENOMEM; - - prm->ureq[i].req = req; - prm->ureq[i].pp = prm; - - req->zero = 0; - req->context = &prm->ureq[i]; - req->length = req_len; - req->complete = agdev_iso_complete; - req->buf = prm->rbuf + i * prm->max_psize; - } - - if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) - dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - } - - return 0; + return ret; } static int afunc_get_alt(struct usb_function *fn, unsigned intf) { - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; - - if (intf == agdev->ac_intf) - return agdev->ac_alt; - else if (intf == agdev->as_out_intf) - return agdev->as_out_alt; - else if (intf == agdev->as_in_intf) - return agdev->as_in_alt; + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); + + if (intf == uac2->ac_intf) + return uac2->ac_alt; + else if (intf == uac2->as_out_intf) + return uac2->as_out_alt; + else if (intf == uac2->as_in_intf) + return uac2->as_in_alt; else - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d Invalid Interface %d!\n", __func__, __LINE__, intf); @@ -1262,22 +672,19 @@ afunc_get_alt(struct usb_function *fn, unsigned intf) static void afunc_disable(struct usb_function *fn) { - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; - - free_ep(&uac2->p_prm, agdev->in_ep); - agdev->as_in_alt = 0; + struct f_uac2 *uac2 = func_to_uac2(fn); - free_ep(&uac2->c_prm, agdev->out_ep); - agdev->as_out_alt = 0; + uac2->as_in_alt = 0; + uac2->as_out_alt = 0; + u_audio_stop_capture(&uac2->g_audio); + u_audio_stop_playback(&uac2->g_audio); } static int in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_request *req = fn->config->cdev->req; - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct g_audio *agdev = func_to_g_audio(fn); struct f_uac2_opts *opts; u16 w_length = le16_to_cpu(cr->wLength); u16 w_index = le16_to_cpu(cr->wIndex); @@ -1287,7 +694,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) int value = -EOPNOTSUPP; int p_srate, c_srate; - opts = agdev_to_uac2_opts(agdev); + opts = g_audio_to_uac2_opts(agdev); p_srate = opts->p_srate; c_srate = opts->c_srate; @@ -1306,7 +713,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) *(u8 *)req->buf = 1; value = min_t(unsigned, w_length, 1); } else { - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d control_selector=%d TODO!\n", __func__, __LINE__, control_selector); } @@ -1318,8 +725,7 @@ static int in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_request *req = fn->config->cdev->req; - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct g_audio *agdev = func_to_g_audio(fn); struct f_uac2_opts *opts; u16 w_length = le16_to_cpu(cr->wLength); u16 w_index = le16_to_cpu(cr->wIndex); @@ -1330,7 +736,7 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) int value = -EOPNOTSUPP; int p_srate, c_srate; - opts = agdev_to_uac2_opts(agdev); + opts = g_audio_to_uac2_opts(agdev); p_srate = opts->p_srate; c_srate = opts->c_srate; @@ -1349,7 +755,7 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) value = min_t(unsigned, w_length, sizeof r); memcpy(req->buf, &r, value); } else { - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d control_selector=%d TODO!\n", __func__, __LINE__, control_selector); } @@ -1384,13 +790,13 @@ out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) static int setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr) { - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); u16 w_index = le16_to_cpu(cr->wIndex); u8 intf = w_index & 0xff; - if (intf != agdev->ac_intf) { - dev_err(&uac2->pdev.dev, + if (intf != uac2->ac_intf) { + dev_err(&agdev->gadget->dev, "%s:%d Error!\n", __func__, __LINE__); return -EOPNOTSUPP; } @@ -1407,8 +813,7 @@ static int afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_composite_dev *cdev = fn->config->cdev; - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct g_audio *agdev = func_to_g_audio(fn); struct usb_request *req = cdev->req; u16 w_length = le16_to_cpu(cr->wLength); int value = -EOPNOTSUPP; @@ -1420,14 +825,15 @@ afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr) if ((cr->bRequestType & USB_RECIP_MASK) == USB_RECIP_INTERFACE) value = setup_rq_inf(fn, cr); else - dev_err(&uac2->pdev.dev, "%s:%d Error!\n", __func__, __LINE__); + dev_err(&agdev->gadget->dev, "%s:%d Error!\n", + __func__, __LINE__); if (value >= 0) { req->length = value; req->zero = value < w_length; value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); if (value < 0) { - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d Error!\n", __func__, __LINE__); req->status = 0; } @@ -1500,6 +906,7 @@ UAC2_ATTRIBUTE(p_ssize); UAC2_ATTRIBUTE(c_chmask); UAC2_ATTRIBUTE(c_srate); UAC2_ATTRIBUTE(c_ssize); +UAC2_ATTRIBUTE(req_number); static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_p_chmask, @@ -1508,6 +915,7 @@ static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_c_chmask, &f_uac2_opts_attr_c_srate, &f_uac2_opts_attr_c_ssize, + &f_uac2_opts_attr_req_number, NULL, }; @@ -1545,15 +953,16 @@ static struct usb_function_instance *afunc_alloc_inst(void) opts->c_chmask = UAC2_DEF_CCHMASK; opts->c_srate = UAC2_DEF_CSRATE; opts->c_ssize = UAC2_DEF_CSSIZE; + opts->req_number = UAC2_DEF_REQ_NUM; return &opts->func_inst; } static void afunc_free(struct usb_function *f) { - struct audio_dev *agdev; + struct g_audio *agdev; struct f_uac2_opts *opts; - agdev = func_to_agdev(f); + agdev = func_to_g_audio(f); opts = container_of(f->fi, struct f_uac2_opts, func_inst); kfree(agdev); mutex_lock(&opts->lock); @@ -1563,26 +972,21 @@ static void afunc_free(struct usb_function *f) static void afunc_unbind(struct usb_configuration *c, struct usb_function *f) { - struct audio_dev *agdev = func_to_agdev(f); - struct uac2_rtd_params *prm; + struct g_audio *agdev = func_to_g_audio(f); - alsa_uac2_exit(agdev); - - prm = &agdev->uac2.p_prm; - kfree(prm->rbuf); - - prm = &agdev->uac2.c_prm; - kfree(prm->rbuf); + g_audio_cleanup(agdev); usb_free_all_descriptors(f); + + agdev->gadget = NULL; } static struct usb_function *afunc_alloc(struct usb_function_instance *fi) { - struct audio_dev *agdev; + struct f_uac2 *uac2; struct f_uac2_opts *opts; - agdev = kzalloc(sizeof(*agdev), GFP_KERNEL); - if (agdev == NULL) + uac2 = kzalloc(sizeof(*uac2), GFP_KERNEL); + if (uac2 == NULL) return ERR_PTR(-ENOMEM); opts = container_of(fi, struct f_uac2_opts, func_inst); @@ -1590,16 +994,16 @@ static struct usb_function *afunc_alloc(struct usb_function_instance *fi) ++opts->refcnt; mutex_unlock(&opts->lock); - agdev->func.name = "uac2_func"; - agdev->func.bind = afunc_bind; - agdev->func.unbind = afunc_unbind; - agdev->func.set_alt = afunc_set_alt; - agdev->func.get_alt = afunc_get_alt; - agdev->func.disable = afunc_disable; - agdev->func.setup = afunc_setup; - agdev->func.free_func = afunc_free; + uac2->g_audio.func.name = "uac2_func"; + uac2->g_audio.func.bind = afunc_bind; + uac2->g_audio.func.unbind = afunc_unbind; + uac2->g_audio.func.set_alt = afunc_set_alt; + uac2->g_audio.func.get_alt = afunc_get_alt; + uac2->g_audio.func.disable = afunc_disable; + uac2->g_audio.func.setup = afunc_setup; + uac2->g_audio.func.free_func = afunc_free; - return &agdev->func; + return &uac2->g_audio.func; } DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc); diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h index 78dd37279bd4..19eeb83538a5 100644 --- a/drivers/usb/gadget/function/u_uac2.h +++ b/drivers/usb/gadget/function/u_uac2.h @@ -24,6 +24,7 @@ #define UAC2_DEF_CCHMASK 0x3 #define UAC2_DEF_CSRATE 64000 #define UAC2_DEF_CSSIZE 2 +#define UAC2_DEF_REQ_NUM 2 struct f_uac2_opts { struct usb_function_instance func_inst; @@ -33,6 +34,7 @@ struct f_uac2_opts { int c_chmask; int c_srate; int c_ssize; + int req_number; bool bound; struct mutex lock; diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig index 4d682ad7bf23..4b995c1f9f22 100644 --- a/drivers/usb/gadget/legacy/Kconfig +++ b/drivers/usb/gadget/legacy/Kconfig @@ -54,8 +54,10 @@ config USB_AUDIO depends on SND select USB_LIBCOMPOSITE select SND_PCM - select USB_F_UAC1 if GADGET_UAC1 + select USB_F_UAC1 if (GADGET_UAC1 && !GADGET_UAC1_LEGACY) + select USB_F_UAC1_LEGACY if (GADGET_UAC1 && GADGET_UAC1_LEGACY) select USB_F_UAC2 if !GADGET_UAC1 + select USB_U_AUDIO if (USB_F_UAC2 || USB_F_UAC1) help This Gadget Audio driver is compatible with USB Audio Class specification 2.0. It implements 1 AudioControl interface, @@ -73,10 +75,17 @@ config USB_AUDIO dynamically linked module called "g_audio". config GADGET_UAC1 - bool "UAC 1.0 (Legacy)" + bool "UAC 1.0" depends on USB_AUDIO help - If you instead want older UAC Spec-1.0 driver that also has audio + If you instead want older USB Audio Class specification 1.0 support + with similar driver capabilities. + +config GADGET_UAC1_LEGACY + bool "UAC 1.0 (Legacy)" + depends on GADGET_UAC1 + help + If you instead want legacy UAC Spec-1.0 driver that also has audio paths hardwired to the Audio codec chip on-board and doesn't work without one. diff --git a/drivers/usb/gadget/legacy/audio.c b/drivers/usb/gadget/legacy/audio.c index 685cf3b4b78f..c8682ed59d79 100644 --- a/drivers/usb/gadget/legacy/audio.c +++ b/drivers/usb/gadget/legacy/audio.c @@ -53,8 +53,41 @@ static int c_ssize = UAC2_DEF_CSSIZE; module_param(c_ssize, uint, S_IRUGO); MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)"); #else +#ifndef CONFIG_GADGET_UAC1_LEGACY #include "u_uac1.h" +/* Playback(USB-IN) Default Stereo - Fl/Fr */ +static int p_chmask = UAC1_DEF_PCHMASK; +module_param(p_chmask, uint, S_IRUGO); +MODULE_PARM_DESC(p_chmask, "Playback Channel Mask"); + +/* Playback Default 48 KHz */ +static int p_srate = UAC1_DEF_PSRATE; +module_param(p_srate, uint, S_IRUGO); +MODULE_PARM_DESC(p_srate, "Playback Sampling Rate"); + +/* Playback Default 16bits/sample */ +static int p_ssize = UAC1_DEF_PSSIZE; +module_param(p_ssize, uint, S_IRUGO); +MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)"); + +/* Capture(USB-OUT) Default Stereo - Fl/Fr */ +static int c_chmask = UAC1_DEF_CCHMASK; +module_param(c_chmask, uint, S_IRUGO); +MODULE_PARM_DESC(c_chmask, "Capture Channel Mask"); + +/* Capture Default 48 KHz */ +static int c_srate = UAC1_DEF_CSRATE; +module_param(c_srate, uint, S_IRUGO); +MODULE_PARM_DESC(c_srate, "Capture Sampling Rate"); + +/* Capture Default 16bits/sample */ +static int c_ssize = UAC1_DEF_CSSIZE; +module_param(c_ssize, uint, S_IRUGO); +MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)"); +#else /* CONFIG_GADGET_UAC1_LEGACY */ +#include "u_uac1_legacy.h" + static char *fn_play = FILE_PCM_PLAYBACK; module_param(fn_play, charp, S_IRUGO); MODULE_PARM_DESC(fn_play, "Playback PCM device file name"); @@ -78,6 +111,7 @@ MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count"); static int audio_buf_size = UAC1_AUDIO_BUF_SIZE; module_param(audio_buf_size, int, S_IRUGO); MODULE_PARM_DESC(audio_buf_size, "Audio buffer size"); +#endif /* CONFIG_GADGET_UAC1_LEGACY */ #endif /* string IDs are assigned dynamically */ @@ -125,7 +159,7 @@ static struct usb_device_descriptor device_desc = { .bcdUSB = cpu_to_le16(0x200), -#ifdef CONFIG_GADGET_UAC1 +#ifdef CONFIG_GADGET_UAC1_LEGACY .bDeviceClass = USB_CLASS_PER_INTERFACE, .bDeviceSubClass = 0, .bDeviceProtocol = 0, @@ -207,7 +241,11 @@ static int audio_bind(struct usb_composite_dev *cdev) #ifndef CONFIG_GADGET_UAC1 struct f_uac2_opts *uac2_opts; #else +#ifndef CONFIG_GADGET_UAC1_LEGACY struct f_uac1_opts *uac1_opts; +#else + struct f_uac1_legacy_opts *uac1_opts; +#endif #endif int status; @@ -216,7 +254,11 @@ static int audio_bind(struct usb_composite_dev *cdev) if (IS_ERR(fi_uac2)) return PTR_ERR(fi_uac2); #else +#ifndef CONFIG_GADGET_UAC1_LEGACY fi_uac1 = usb_get_function_instance("uac1"); +#else + fi_uac1 = usb_get_function_instance("uac1_legacy"); +#endif if (IS_ERR(fi_uac1)) return PTR_ERR(fi_uac1); #endif @@ -229,14 +271,26 @@ static int audio_bind(struct usb_composite_dev *cdev) uac2_opts->c_chmask = c_chmask; uac2_opts->c_srate = c_srate; uac2_opts->c_ssize = c_ssize; + uac2_opts->req_number = UAC2_DEF_REQ_NUM; #else +#ifndef CONFIG_GADGET_UAC1_LEGACY uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst); + uac1_opts->p_chmask = p_chmask; + uac1_opts->p_srate = p_srate; + uac1_opts->p_ssize = p_ssize; + uac1_opts->c_chmask = c_chmask; + uac1_opts->c_srate = c_srate; + uac1_opts->c_ssize = c_ssize; + uac1_opts->req_number = UAC1_DEF_REQ_NUM; +#else /* CONFIG_GADGET_UAC1_LEGACY */ + uac1_opts = container_of(fi_uac1, struct f_uac1_legacy_opts, func_inst); uac1_opts->fn_play = fn_play; uac1_opts->fn_cap = fn_cap; uac1_opts->fn_cntl = fn_cntl; uac1_opts->req_buf_size = req_buf_size; uac1_opts->req_count = req_count; uac1_opts->audio_buf_size = audio_buf_size; +#endif /* CONFIG_GADGET_UAC1_LEGACY */ #endif status = usb_string_ids_tab(cdev, strings_dev); diff --git a/drivers/video/fbdev/msm/mdss_mdp_ctl.c b/drivers/video/fbdev/msm/mdss_mdp_ctl.c index 710aebbd9c59..e86f08810ba1 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_ctl.c +++ b/drivers/video/fbdev/msm/mdss_mdp_ctl.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -6067,10 +6067,11 @@ int mdss_mdp_display_commit(struct mdss_mdp_ctl *ctl, void *arg, !bitmap_empty(mdata->bwc_enable_map, MAX_DRV_SUP_PIPES)) mdss_mdp_bwcpanic_ctrl(mdata, true); - ret = mdss_mdp_cwb_setup(ctl); - if (ret) - pr_warn("concurrent setup failed ctl=%d\n", ctl->num); - + if (mdata->mdp_rev >= MDSS_MDP_HW_REV_300) { + ret = mdss_mdp_cwb_setup(ctl); + if (ret) + pr_warn("concurrent setup failed ctl=%d\n", ctl->num); + } ctl_flush_bits |= ctl->flush_bits; ATRACE_BEGIN("flush_kickoff"); diff --git a/drivers/video/fbdev/msm/mdss_mdp_intf_writeback.c b/drivers/video/fbdev/msm/mdss_mdp_intf_writeback.c index 9b63499e64b0..c1ea9b431606 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_intf_writeback.c +++ b/drivers/video/fbdev/msm/mdss_mdp_intf_writeback.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2017, 2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -1020,8 +1020,8 @@ static int mdss_mdp_writeback_display(struct mdss_mdp_ctl *ctl, void *arg) if (ctl->mdata->default_ot_wr_limit || ctl->mdata->default_ot_rd_limit) mdss_mdp_set_ot_limit_wb(ctx, true); - - mdss_mdp_set_qos_wb(ctl, ctx); + if (ctl->mdata->mdp_rev >= MDSS_MDP_HW_REV_300) + mdss_mdp_set_qos_wb(ctl, ctx); wb_args = (struct mdss_mdp_writeback_arg *) arg; if (!wb_args) diff --git a/drivers/video/fbdev/msm/mdss_rotator.c b/drivers/video/fbdev/msm/mdss_rotator.c index 78bccdbfee3b..3f1ed5a90183 100644 --- a/drivers/video/fbdev/msm/mdss_rotator.c +++ b/drivers/video/fbdev/msm/mdss_rotator.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2017, 2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -1051,11 +1051,13 @@ static int mdss_rotator_calc_perf(struct mdss_rot_perf *perf) if (!config->input.width || (0xffffffff/config->input.width < config->input.height)) return -EINVAL; + + perf->clk_rate = config->input.width * config->input.height; + if (!perf->clk_rate || (0xffffffff/perf->clk_rate < config->frame_rate)) return -EINVAL; - perf->clk_rate = config->input.width * config->input.height; perf->clk_rate *= config->frame_rate; /* rotator processes 4 pixels per clock */ perf->clk_rate /= 4; |
