summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/gpu/drm/msm/Kconfig10
-rw-r--r--drivers/gpu/drm/msm/Makefile3
-rw-r--r--drivers/gpu/drm/msm/dsi-staging/dsi_display.c20
-rw-r--r--drivers/gpu/drm/msm/dsi-staging/dsi_display.h2
-rw-r--r--drivers/gpu/drm/msm/dsi-staging/dsi_drm.c23
-rw-r--r--drivers/gpu/drm/msm/dsi-staging/dsi_drm.h12
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c44
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h19
-rw-r--r--drivers/gpu/drm/msm/msm_drv.c144
-rw-r--r--drivers/gpu/drm/msm/msm_drv.h2
-rw-r--r--drivers/gpu/drm/msm/msm_kms.h3
-rw-r--r--drivers/gpu/drm/msm/sde/sde_connector.c20
-rw-r--r--drivers/gpu/drm/msm/sde/sde_connector.h14
-rw-r--r--drivers/gpu/drm/msm/sde/sde_crtc.c53
-rw-r--r--drivers/gpu/drm/msm/sde/sde_crtc.h4
-rw-r--r--drivers/gpu/drm/msm/sde/sde_encoder.c57
-rw-r--r--drivers/gpu/drm/msm/sde/sde_encoder_phys.h38
-rw-r--r--drivers/gpu/drm/msm/sde/sde_encoder_phys_shd.c1004
-rw-r--r--drivers/gpu/drm/msm/sde/sde_kms.c91
-rw-r--r--drivers/gpu/drm/msm/sde/sde_kms.h2
-rw-r--r--drivers/gpu/drm/msm/sde/sde_plane.c21
-rw-r--r--drivers/gpu/drm/msm/sde/sde_rm.c118
-rw-r--r--drivers/gpu/drm/msm/sde/sde_rm.h28
-rw-r--r--drivers/gpu/drm/msm/sde/sde_shd.c1090
-rw-r--r--drivers/gpu/drm/msm/sde/sde_shd.h230
-rw-r--r--drivers/gpu/drm/msm/sde/sde_splash.c49
-rw-r--r--drivers/media/platform/msm/ais/ispif/msm_ispif.c45
-rw-r--r--drivers/media/platform/msm/camera_v2/common/Makefile2
-rw-r--r--drivers/media/platform/msm/camera_v2/common/msm_cam_cx_ipeak.c89
-rw-r--r--drivers/media/platform/msm/camera_v2/common/msm_cam_cx_ipeak.h24
-rw-r--r--drivers/media/platform/msm/camera_v2/ispif/msm_ispif.c44
-rw-r--r--drivers/media/platform/msm/vidc/msm_vdec.c27
-rw-r--r--drivers/media/platform/msm/vidc/msm_vidc_common.c14
-rw-r--r--drivers/media/platform/msm/vidc/msm_vidc_internal.h1
-rw-r--r--drivers/net/wireless/cnss2/bus.c4
-rw-r--r--drivers/net/wireless/cnss2/main.c3
-rw-r--r--drivers/net/wireless/cnss2/pci.c1
-rw-r--r--drivers/net/wireless/cnss2/qmi.c16
-rw-r--r--drivers/net/wireless/cnss2/usb.c23
-rw-r--r--drivers/net/wireless/cnss2/wlan_firmware_service_v01.c18
-rw-r--r--drivers/net/wireless/cnss2/wlan_firmware_service_v01.h4
-rw-r--r--drivers/soc/qcom/hab/hab_mem_linux.c5
-rw-r--r--drivers/usb/gadget/Kconfig1
-rw-r--r--drivers/usb/gadget/function/f_hid.c284
-rw-r--r--drivers/usb/gadget/function/f_printer.c6
-rw-r--r--drivers/usb/gadget/function/f_uac2.c806
-rw-r--r--drivers/usb/gadget/function/u_uac2.h2
-rw-r--r--drivers/usb/gadget/legacy/Kconfig15
-rw-r--r--drivers/usb/gadget/legacy/audio.c56
-rw-r--r--drivers/video/fbdev/msm/mdss_mdp_ctl.c11
-rw-r--r--drivers/video/fbdev/msm/mdss_mdp_intf_writeback.c6
-rw-r--r--drivers/video/fbdev/msm/mdss_rotator.c6
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;