summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/clk/msm/clock-cpu-8996.c25
-rw-r--r--drivers/clk/msm/clock-gcc-8996.c8
-rw-r--r--drivers/gpu/drm/msm/Makefile1
-rw-r--r--drivers/gpu/drm/msm/adreno/a5xx_gpu.c2
-rw-r--r--drivers/gpu/drm/msm/dba_bridge.c357
-rw-r--r--drivers/gpu/drm/msm/dba_bridge.h67
-rw-r--r--drivers/gpu/drm/msm/dsi-staging/dsi_ctrl_hw_1_4.c29
-rw-r--r--drivers/gpu/drm/msm/dsi-staging/dsi_defs.h4
-rw-r--r--drivers/gpu/drm/msm/dsi-staging/dsi_display.c379
-rw-r--r--drivers/gpu/drm/msm/dsi-staging/dsi_display.h13
-rw-r--r--drivers/gpu/drm/msm/dsi-staging/dsi_drm.c119
-rw-r--r--drivers/gpu/drm/msm/dsi-staging/dsi_panel.c59
-rw-r--r--drivers/gpu/drm/msm/dsi-staging/dsi_panel.h16
-rw-r--r--drivers/gpu/drm/msm/msm_smmu.c17
-rw-r--r--drivers/gpu/drm/msm/sde/sde_backlight.c12
-rw-r--r--drivers/gpu/drm/msm/sde/sde_connector.c8
-rw-r--r--drivers/gpu/drm/msm/sde/sde_formats.c6
-rw-r--r--drivers/gpu/drm/msm/sde/sde_kms.c6
-rw-r--r--drivers/iommu/Kconfig2
-rw-r--r--drivers/iommu/arm-smmu.c4
-rw-r--r--drivers/iommu/iommu-debug.c8
-rw-r--r--drivers/net/can/spi/rh850.c615
-rw-r--r--drivers/platform/msm/gpio-usbdetect.c142
-rw-r--r--drivers/soc/qcom/Kconfig42
-rw-r--r--drivers/soc/qcom/Makefile2
-rw-r--r--drivers/soc/qcom/boot_marker.c183
-rw-r--r--drivers/soc/qcom/boot_stats.c60
-rw-r--r--drivers/soc/qcom/cache_m4m_erp64.c635
-rw-r--r--drivers/soc/qcom/scm-boot.c19
-rw-r--r--drivers/soc/qcom/socinfo.c36
-rw-r--r--drivers/usb/misc/Kconfig10
-rw-r--r--drivers/usb/misc/Makefile2
-rw-r--r--drivers/usb/misc/ks_bridge.c1105
-rw-r--r--drivers/video/fbdev/Makefile6
-rw-r--r--drivers/video/fbdev/msm/msm_dba/adv7533.c8
-rw-r--r--drivers/video/fbdev/msm/msm_dba/msm_dba_helpers.c4
36 files changed, 3706 insertions, 305 deletions
diff --git a/drivers/clk/msm/clock-cpu-8996.c b/drivers/clk/msm/clock-cpu-8996.c
index bcda6f31d6f5..bca8ada97f7d 100644
--- a/drivers/clk/msm/clock-cpu-8996.c
+++ b/drivers/clk/msm/clock-cpu-8996.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014-2017, 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
@@ -1308,6 +1308,7 @@ static int cpu_clock_8996_driver_probe(struct platform_device *pdev)
unsigned long pwrclrate, perfclrate, cbfrate;
int pvs_ver = 0;
u32 pte_efuse;
+ u32 clk_rate;
char perfclspeedbinstr[] = "qcom,perfcl-speedbinXX-vXX";
char pwrclspeedbinstr[] = "qcom,pwrcl-speedbinXX-vXX";
char cbfspeedbinstr[] = "qcom,cbf-speedbinXX-vXX";
@@ -1435,6 +1436,18 @@ static int cpu_clock_8996_driver_probe(struct platform_device *pdev)
clk_prepare_enable(&pwrcl_alt_pll.c);
clk_prepare_enable(&cbf_pll.c);
+ /* Override the existing ealry boot frequency for power cluster */
+ ret = of_property_read_u32(pdev->dev.of_node,
+ "qcom,pwrcl-early-boot-freq", &clk_rate);
+ if (!ret)
+ pwrcl_early_boot_rate = clk_rate;
+
+ /* Override the existing ealry boot frequency for perf cluster */
+ ret = of_property_read_u32(pdev->dev.of_node,
+ "qcom,perfcl-early-boot-freq", &clk_rate);
+ if (!ret)
+ perfcl_early_boot_rate = clk_rate;
+
/* Set the early boot rate. This may also switch us to the ACD leg */
clk_set_rate(&pwrcl_clk.c, pwrcl_early_boot_rate);
clk_set_rate(&perfcl_clk.c, perfcl_early_boot_rate);
@@ -1450,6 +1463,7 @@ static struct of_device_id match_table[] = {
{ .compatible = "qcom,cpu-clock-8996" },
{ .compatible = "qcom,cpu-clock-8996-v3" },
{ .compatible = "qcom,cpu-clock-8996-pro" },
+ { .compatible = "qcom,cpu-clock-8996-auto" },
{}
};
@@ -1499,6 +1513,9 @@ module_exit(cpu_clock_8996_exit);
#define HF_MUX_SEL_LF_MUX 0x1
#define LF_MUX_SEL_ALT_PLL 0x1
+#define PWRCL_EARLY_BOOT_RATE 1286400000
+#define PERFCL_EARLY_BOOT_RATE 1363200000
+
static int use_alt_pll;
module_param(use_alt_pll, int, 0444);
@@ -1537,6 +1554,12 @@ int __init cpu_clock_8996_early_init(void)
cpu_clocks_v3 = true;
cpu_clocks_pro = true;
} else if (of_find_compatible_node(NULL, NULL,
+ "qcom,cpu-clock-8996-auto")) {
+ cpu_clocks_v3 = true;
+ cpu_clocks_pro = true;
+ pwrcl_early_boot_rate = PWRCL_EARLY_BOOT_RATE;
+ perfcl_early_boot_rate = PERFCL_EARLY_BOOT_RATE;
+ } else if (of_find_compatible_node(NULL, NULL,
"qcom,cpu-clock-8996-v3")) {
cpu_clocks_v3 = true;
} else if (!of_find_compatible_node(NULL, NULL,
diff --git a/drivers/clk/msm/clock-gcc-8996.c b/drivers/clk/msm/clock-gcc-8996.c
index e93e9c494023..6dd2cf879c49 100644
--- a/drivers/clk/msm/clock-gcc-8996.c
+++ b/drivers/clk/msm/clock-gcc-8996.c
@@ -3670,14 +3670,6 @@ static int msm_gcc_8996_probe(struct platform_device *pdev)
regval |= BIT(21);
writel_relaxed(regval, virt_base + GCC_APCS_CLOCK_BRANCH_ENA_VOTE);
- /*
- * Set the HMSS_AHB_CLK_SLEEP_ENA bit to allow the hmss_ahb_clk to be
- * turned off by hardware during certain apps low power modes.
- */
- regval = readl_relaxed(virt_base + GCC_APCS_CLOCK_SLEEP_ENA_VOTE);
- regval |= BIT(21);
- writel_relaxed(regval, virt_base + GCC_APCS_CLOCK_SLEEP_ENA_VOTE);
-
vdd_dig.vdd_uv[1] = RPM_REGULATOR_CORNER_SVS_KRAIT;
vdd_dig.regulator[0] = devm_regulator_get(&pdev->dev, "vdd_dig");
if (IS_ERR(vdd_dig.regulator[0])) {
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 3d0617dbc514..f3a8a8416c7a 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -49,6 +49,7 @@ msm_drm-y := \
sde/sde_vbif.o \
sde_dbg_evtlog.o \
sde_io_util.o \
+ dba_bridge.o \
sde_edid_parser.o
# use drm gpu driver only if qcom_kgsl driver not available
diff --git a/drivers/gpu/drm/msm/adreno/a5xx_gpu.c b/drivers/gpu/drm/msm/adreno/a5xx_gpu.c
index 9ceef8f437b5..359dff228202 100644
--- a/drivers/gpu/drm/msm/adreno/a5xx_gpu.c
+++ b/drivers/gpu/drm/msm/adreno/a5xx_gpu.c
@@ -1163,7 +1163,7 @@ static const u32 a5xx_registers[] = {
0xe9c0, 0xe9c7, 0xe9d0, 0xe9d1, 0xea00, 0xea01, 0xea10, 0xea1c,
0xea40, 0xea68, 0xea80, 0xea80, 0xea82, 0xeaa3, 0xeaa5, 0xeac2,
0xeb80, 0xeb8f, 0xebb0, 0xebb0, 0xec00, 0xec05, 0xec08, 0xece9,
- 0xecf0, 0xecf0, 0xf400, 0xf400, 0xf800, 0xf807,
+ 0xecf0, 0xecf0, 0xf800, 0xf807,
~0
};
diff --git a/drivers/gpu/drm/msm/dba_bridge.c b/drivers/gpu/drm/msm/dba_bridge.c
new file mode 100644
index 000000000000..f933a7f3dcfb
--- /dev/null
+++ b/drivers/gpu/drm/msm/dba_bridge.c
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2016-2017, 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 <video/msm_dba.h>
+#include "drm_edid.h"
+#include "sde_kms.h"
+#include "dba_bridge.h"
+
+#undef pr_fmt
+#define pr_fmt(fmt) "dba_bridge:[%s] " fmt, __func__
+
+/**
+ * struct dba_bridge - DBA bridge information
+ * @base: drm_bridge base
+ * @client_name: Client's name who calls the init
+ * @chip_name: Bridge chip name
+ * @name: Bridge chip name
+ * @id: Bridge driver index
+ * @display: Private display handle
+ * @list: Bridge chip driver list node
+ * @ops: DBA operation container
+ * @dba_ctx: DBA context
+ * @mode: DRM mode info
+ * @hdmi_mode: HDMI or DVI mode for the sink
+ * @num_of_input_lanes: Number of input lanes in case of DSI/LVDS
+ * @pluggable: If it's pluggable
+ * @panel_count: Number of panels attached to this display
+ */
+struct dba_bridge {
+ struct drm_bridge base;
+ char client_name[MSM_DBA_CLIENT_NAME_LEN];
+ char chip_name[MSM_DBA_CHIP_NAME_MAX_LEN];
+ u32 id;
+ void *display;
+ struct list_head list;
+ struct msm_dba_ops ops;
+ void *dba_ctx;
+ struct drm_display_mode mode;
+ bool hdmi_mode;
+ u32 num_of_input_lanes;
+ bool pluggable;
+ u32 panel_count;
+};
+#define to_dba_bridge(x) container_of((x), struct dba_bridge, base)
+
+static void _dba_bridge_cb(void *data, enum msm_dba_callback_event event)
+{
+ struct dba_bridge *d_bridge = data;
+
+ if (!d_bridge) {
+ SDE_ERROR("Invalid data\n");
+ return;
+ }
+
+ DRM_DEBUG("event: %d\n", event);
+
+ switch (event) {
+ case MSM_DBA_CB_HPD_CONNECT:
+ DRM_DEBUG("HPD CONNECT\n");
+ break;
+ case MSM_DBA_CB_HPD_DISCONNECT:
+ DRM_DEBUG("HPD DISCONNECT\n");
+ break;
+ default:
+ DRM_DEBUG("event:%d is not supported\n", event);
+ break;
+ }
+}
+
+static int _dba_bridge_attach(struct drm_bridge *bridge)
+{
+ struct dba_bridge *d_bridge = to_dba_bridge(bridge);
+ struct msm_dba_reg_info info;
+ int ret = 0;
+
+ if (!bridge) {
+ SDE_ERROR("Invalid params\n");
+ return -EINVAL;
+ }
+
+ memset(&info, 0, sizeof(info));
+ /* initialize DBA registration data */
+ strlcpy(info.client_name, d_bridge->client_name,
+ MSM_DBA_CLIENT_NAME_LEN);
+ strlcpy(info.chip_name, d_bridge->chip_name,
+ MSM_DBA_CHIP_NAME_MAX_LEN);
+ info.instance_id = d_bridge->id;
+ info.cb = _dba_bridge_cb;
+ info.cb_data = d_bridge;
+
+ /* register client with DBA and get device's ops*/
+ if (IS_ENABLED(CONFIG_MSM_DBA)) {
+ d_bridge->dba_ctx = msm_dba_register_client(&info,
+ &d_bridge->ops);
+ if (IS_ERR_OR_NULL(d_bridge->dba_ctx)) {
+ SDE_ERROR("dba register failed\n");
+ ret = PTR_ERR(d_bridge->dba_ctx);
+ goto error;
+ }
+ } else {
+ SDE_ERROR("DBA not enabled\n");
+ ret = -ENODEV;
+ goto error;
+ }
+
+ DRM_INFO("client:%s bridge:[%s:%d] attached\n",
+ d_bridge->client_name, d_bridge->chip_name, d_bridge->id);
+
+error:
+ return ret;
+}
+
+static void _dba_bridge_pre_enable(struct drm_bridge *bridge)
+{
+ if (!bridge) {
+ SDE_ERROR("Invalid params\n");
+ return;
+ }
+}
+
+static void _dba_bridge_enable(struct drm_bridge *bridge)
+{
+ int rc = 0;
+ struct dba_bridge *d_bridge = to_dba_bridge(bridge);
+ struct msm_dba_video_cfg video_cfg;
+ struct drm_display_mode *mode;
+ struct hdmi_avi_infoframe avi_frame;
+
+ if (!bridge) {
+ SDE_ERROR("Invalid params\n");
+ return;
+ }
+
+ memset(&video_cfg, 0, sizeof(video_cfg));
+ memset(&avi_frame, 0, sizeof(avi_frame));
+ mode = &d_bridge->mode;
+ video_cfg.h_active = mode->hdisplay;
+ video_cfg.v_active = mode->vdisplay;
+ video_cfg.h_front_porch = mode->hsync_start - mode->hdisplay;
+ video_cfg.v_front_porch = mode->vsync_start - mode->vdisplay;
+ video_cfg.h_back_porch = mode->htotal - mode->hsync_end;
+ video_cfg.v_back_porch = mode->vtotal - mode->vsync_end;
+ video_cfg.h_pulse_width = mode->hsync_end - mode->hsync_start;
+ video_cfg.v_pulse_width = mode->vsync_end - mode->vsync_start;
+ video_cfg.pclk_khz = mode->clock;
+ video_cfg.hdmi_mode = d_bridge->hdmi_mode;
+ video_cfg.num_of_input_lanes = d_bridge->num_of_input_lanes;
+
+ SDE_DEBUG(
+ "video=h[%d,%d,%d,%d] v[%d,%d,%d,%d] pclk=%d hdmi=%d lane=%d\n",
+ video_cfg.h_active, video_cfg.h_front_porch,
+ video_cfg.h_pulse_width, video_cfg.h_back_porch,
+ video_cfg.v_active, video_cfg.v_front_porch,
+ video_cfg.v_pulse_width, video_cfg.v_back_porch,
+ video_cfg.pclk_khz, video_cfg.hdmi_mode,
+ video_cfg.num_of_input_lanes);
+
+ rc = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame, mode);
+ if (rc) {
+ SDE_ERROR("get avi frame failed ret=%d\n", rc);
+ } else {
+ video_cfg.scaninfo = avi_frame.scan_mode;
+ switch (avi_frame.picture_aspect) {
+ case HDMI_PICTURE_ASPECT_4_3:
+ video_cfg.ar = MSM_DBA_AR_4_3;
+ break;
+ case HDMI_PICTURE_ASPECT_16_9:
+ video_cfg.ar = MSM_DBA_AR_16_9;
+ break;
+ default:
+ break;
+ }
+ video_cfg.vic = avi_frame.video_code;
+ DRM_INFO("scaninfo=%d ar=%d vic=%d\n",
+ video_cfg.scaninfo, video_cfg.ar, video_cfg.vic);
+ }
+
+ if (d_bridge->ops.video_on) {
+ rc = d_bridge->ops.video_on(d_bridge->dba_ctx, true,
+ &video_cfg, 0);
+ if (rc)
+ SDE_ERROR("video on failed ret=%d\n", rc);
+ }
+}
+
+static void _dba_bridge_disable(struct drm_bridge *bridge)
+{
+ int rc = 0;
+ struct dba_bridge *d_bridge = to_dba_bridge(bridge);
+
+ if (!bridge) {
+ SDE_ERROR("Invalid params\n");
+ return;
+ }
+
+ if (d_bridge->ops.video_on) {
+ rc = d_bridge->ops.video_on(d_bridge->dba_ctx, false, NULL, 0);
+ if (rc)
+ SDE_ERROR("video off failed ret=%d\n", rc);
+ }
+}
+
+static void _dba_bridge_post_disable(struct drm_bridge *bridge)
+{
+ if (!bridge) {
+ SDE_ERROR("Invalid params\n");
+ return;
+ }
+}
+
+static void _dba_bridge_mode_set(struct drm_bridge *bridge,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct dba_bridge *d_bridge = to_dba_bridge(bridge);
+
+ if (!bridge || !mode || !adjusted_mode || !d_bridge) {
+ SDE_ERROR("Invalid params\n");
+ return;
+ } else if (!d_bridge->panel_count) {
+ SDE_ERROR("Panel count is 0\n");
+ return;
+ }
+
+ d_bridge->mode = *adjusted_mode;
+ /* Adjust mode according to number of panels */
+ d_bridge->mode.hdisplay /= d_bridge->panel_count;
+ d_bridge->mode.hsync_start /= d_bridge->panel_count;
+ d_bridge->mode.hsync_end /= d_bridge->panel_count;
+ d_bridge->mode.htotal /= d_bridge->panel_count;
+ d_bridge->mode.clock /= d_bridge->panel_count;
+}
+
+static bool _dba_bridge_mode_fixup(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ bool ret = true;
+
+ if (!bridge || !mode || !adjusted_mode) {
+ SDE_ERROR("Invalid params\n");
+ return false;
+ }
+
+ return ret;
+}
+
+static const struct drm_bridge_funcs _dba_bridge_ops = {
+ .attach = _dba_bridge_attach,
+ .mode_fixup = _dba_bridge_mode_fixup,
+ .pre_enable = _dba_bridge_pre_enable,
+ .enable = _dba_bridge_enable,
+ .disable = _dba_bridge_disable,
+ .post_disable = _dba_bridge_post_disable,
+ .mode_set = _dba_bridge_mode_set,
+};
+
+struct drm_bridge *dba_bridge_init(struct drm_device *dev,
+ struct drm_encoder *encoder,
+ struct dba_bridge_init *data)
+{
+ int rc = 0;
+ struct dba_bridge *bridge;
+ struct msm_drm_private *priv = NULL;
+
+ if (!dev || !encoder || !data) {
+ SDE_ERROR("dev=%pK or encoder=%pK or data=%pK is NULL\n",
+ dev, encoder, data);
+ rc = -EINVAL;
+ goto error;
+ }
+
+ priv = dev->dev_private;
+ if (!priv) {
+ SDE_ERROR("Private data is not present\n");
+ rc = -EINVAL;
+ goto error;
+ }
+
+ bridge = kzalloc(sizeof(*bridge), GFP_KERNEL);
+ if (!bridge) {
+ SDE_ERROR("out of memory\n");
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ INIT_LIST_HEAD(&bridge->list);
+ strlcpy(bridge->client_name, data->client_name,
+ MSM_DBA_CLIENT_NAME_LEN);
+ strlcpy(bridge->chip_name, data->chip_name,
+ MSM_DBA_CHIP_NAME_MAX_LEN);
+ bridge->id = data->id;
+ bridge->display = data->display;
+ bridge->hdmi_mode = data->hdmi_mode;
+ bridge->num_of_input_lanes = data->num_of_input_lanes;
+ bridge->pluggable = data->pluggable;
+ bridge->panel_count = data->panel_count;
+ bridge->base.funcs = &_dba_bridge_ops;
+ bridge->base.encoder = encoder;
+
+ rc = drm_bridge_attach(dev, &bridge->base);
+ if (rc) {
+ SDE_ERROR("failed to attach bridge, rc=%d\n", rc);
+ goto error_free_bridge;
+ }
+
+ if (data->precede_bridge) {
+ /* Insert current bridge */
+ bridge->base.next = data->precede_bridge->next;
+ data->precede_bridge->next = &bridge->base;
+ } else {
+ encoder->bridge = &bridge->base;
+ }
+
+ if (!bridge->pluggable) {
+ if (bridge->ops.power_on)
+ bridge->ops.power_on(bridge->dba_ctx, true, 0);
+ if (bridge->ops.check_hpd)
+ bridge->ops.check_hpd(bridge->dba_ctx, 0);
+ }
+
+ return &bridge->base;
+
+error_free_bridge:
+ kfree(bridge);
+error:
+ return ERR_PTR(rc);
+}
+
+void dba_bridge_cleanup(struct drm_bridge *bridge)
+{
+ struct dba_bridge *d_bridge = to_dba_bridge(bridge);
+
+ if (!bridge)
+ return;
+
+ if (IS_ENABLED(CONFIG_MSM_DBA)) {
+ if (!IS_ERR_OR_NULL(d_bridge->dba_ctx))
+ msm_dba_deregister_client(d_bridge->dba_ctx);
+ }
+
+ if (d_bridge->base.encoder)
+ d_bridge->base.encoder->bridge = NULL;
+
+ kfree(bridge);
+}
diff --git a/drivers/gpu/drm/msm/dba_bridge.h b/drivers/gpu/drm/msm/dba_bridge.h
new file mode 100644
index 000000000000..5562d2b2aef9
--- /dev/null
+++ b/drivers/gpu/drm/msm/dba_bridge.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2016-2017, 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 _DBA_BRIDGE_H_
+#define _DBA_BRIDGE_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 dba_bridge_init - Init parameters for DBA bridge
+ * @client_name: Client's name who calls the init
+ * @chip_name: Bridge chip name
+ * @id: Bridge driver index
+ * @display: Private display handle
+ * @hdmi_mode: HDMI or DVI mode for the sink
+ * @num_of_input_lanes: Number of input lanes in case of DSI/LVDS
+ * @precede_bridge: Precede bridge chip
+ * @pluggable: If it's pluggable
+ * @panel_count: Number of panels attached to this display
+ */
+struct dba_bridge_init {
+ const char *client_name;
+ const char *chip_name;
+ u32 id;
+ void *display;
+ bool hdmi_mode;
+ u32 num_of_input_lanes;
+ struct drm_bridge *precede_bridge;
+ bool pluggable;
+ u32 panel_count;
+};
+
+/**
+ * dba_bridge_init - Initialize the DBA bridge
+ * @dev: Pointer to drm device handle
+ * @encoder: Pointer to drm encoder handle
+ * @data: Pointer to init data
+ * Returns: pointer of struct drm_bridge
+ */
+struct drm_bridge *dba_bridge_init(struct drm_device *dev,
+ struct drm_encoder *encoder,
+ struct dba_bridge_init *data);
+
+/**
+ * dba_bridge_cleanup - Clean up the DBA bridge
+ * @bridge: Pointer to DBA bridge handle
+ * Returns: void
+ */
+void dba_bridge_cleanup(struct drm_bridge *bridge);
+
+#endif /* _DBA_BRIDGE_H_ */
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl_hw_1_4.c b/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl_hw_1_4.c
index ca04eedd6af1..2f0f6c2f1b01 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl_hw_1_4.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl_hw_1_4.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2015-2017, 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
@@ -85,6 +85,14 @@ void dsi_ctrl_hw_14_host_setup(struct dsi_ctrl_hw *ctrl,
DSI_W32(ctrl, DSI_CTRL, reg_value);
+ /* Force clock lane in HS */
+ reg_value = DSI_R32(ctrl, DSI_LANE_CTRL);
+ if (cfg->force_clk_lane_hs)
+ reg_value |= BIT(28);
+ else
+ reg_value &= ~BIT(28);
+ DSI_W32(ctrl, DSI_LANE_CTRL, reg_value);
+
pr_debug("[DSI_%d]Host configuration complete\n", ctrl->index);
}
@@ -604,8 +612,9 @@ void dsi_ctrl_hw_14_ulps_request(struct dsi_ctrl_hw *ctrl, u32 lanes)
{
u32 reg = 0;
+ reg = DSI_R32(ctrl, DSI_LANE_CTRL);
if (lanes & DSI_CLOCK_LANE)
- reg = BIT(4);
+ reg |= BIT(4);
if (lanes & DSI_DATA_LANE_0)
reg |= BIT(0);
if (lanes & DSI_DATA_LANE_1)
@@ -664,7 +673,8 @@ void dsi_ctrl_hw_14_clear_ulps_request(struct dsi_ctrl_hw *ctrl, u32 lanes)
u32 reg = 0;
reg = DSI_R32(ctrl, DSI_LANE_CTRL);
- reg &= ~BIT(4); /* clock lane */
+ if (lanes & DSI_CLOCK_LANE)
+ reg &= ~BIT(4); /* clock lane */
if (lanes & DSI_DATA_LANE_0)
reg &= ~BIT(0);
if (lanes & DSI_DATA_LANE_1)
@@ -679,7 +689,18 @@ void dsi_ctrl_hw_14_clear_ulps_request(struct dsi_ctrl_hw *ctrl, u32 lanes)
* HPG recommends separate writes for clearing ULPS_REQUEST and
* ULPS_EXIT.
*/
- DSI_W32(ctrl, DSI_LANE_CTRL, 0x0);
+ reg = DSI_R32(ctrl, DSI_LANE_CTRL);
+ if (lanes & DSI_CLOCK_LANE)
+ reg &= ~BIT(12);
+ if (lanes & DSI_DATA_LANE_0)
+ reg &= ~BIT(8);
+ if (lanes & DSI_DATA_LANE_1)
+ reg &= ~BIT(9);
+ if (lanes & DSI_DATA_LANE_2)
+ reg &= ~BIT(10);
+ if (lanes & DSI_DATA_LANE_3)
+ reg &= ~BIT(11);
+ DSI_W32(ctrl, DSI_LANE_CTRL, reg);
pr_debug("[DSI_%d] ULPS request cleared\n", ctrl->index);
}
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_defs.h b/drivers/gpu/drm/msm/dsi-staging/dsi_defs.h
index 2caa32ea8f0c..d9fcec60693d 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_defs.h
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_defs.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016-2017, 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
@@ -259,6 +259,7 @@ struct dsi_lane_mapping {
* @ignore_rx_eot: Ignore Rx EOT packets if set to true.
* @append_tx_eot: Append EOT packets for forward transmissions if set to
* true.
+ * @force_clk_lane_hs: Force clock lane in high speed mode.
*/
struct dsi_host_common_cfg {
enum dsi_pixel_format dst_format;
@@ -277,6 +278,7 @@ struct dsi_host_common_cfg {
u32 t_clk_pre;
bool ignore_rx_eot;
bool append_tx_eot;
+ bool force_clk_lane_hs;
};
/**
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_display.c b/drivers/gpu/drm/msm/dsi-staging/dsi_display.c
index 5a166a4bae93..f2412daee8b6 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_display.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_display.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016-2017, 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
@@ -18,13 +18,16 @@
#include <linux/of.h>
#include "msm_drv.h"
+#include "sde_kms.h"
#include "dsi_display.h"
#include "dsi_panel.h"
#include "dsi_ctrl.h"
#include "dsi_ctrl_hw.h"
#include "dsi_drm.h"
+#include "dba_bridge.h"
#define to_dsi_display(x) container_of(x, struct dsi_display, host)
+#define DSI_DBA_CLIENT_NAME "dsi"
static DEFINE_MUTEX(dsi_display_list_lock);
static LIST_HEAD(dsi_display_list);
@@ -45,7 +48,7 @@ int dsi_display_set_backlight(void *display, u32 bl_lvl)
if (dsi_display == NULL)
return -EINVAL;
- panel = dsi_display->panel;
+ panel = dsi_display->panel[0];
rc = dsi_panel_set_backlight(panel, bl_lvl);
if (rc)
@@ -87,8 +90,9 @@ static ssize_t debugfs_dump_info_read(struct file *file,
display->ctrl[i].phy->name);
}
- len += snprintf(buf + len, (SZ_4K - len),
- "\tPanel = %s\n", display->panel->name);
+ for (i = 0; i < display->panel_count; i++)
+ len += snprintf(buf + len, (SZ_4K - len),
+ "\tPanel_%d = %s\n", i, display->panel[i]->name);
len += snprintf(buf + len, (SZ_4K - len),
"\tClock master = %s\n",
@@ -1108,7 +1112,7 @@ static int dsi_display_parse_lane_map(struct dsi_display *display)
static int dsi_display_parse_dt(struct dsi_display *display)
{
int rc = 0;
- int i;
+ int i, size;
u32 phy_count = 0;
struct device_node *of_node;
@@ -1151,14 +1155,69 @@ static int dsi_display_parse_dt(struct dsi_display *display)
goto error;
}
- of_node = of_parse_phandle(display->pdev->dev.of_node,
- "qcom,dsi-panel", 0);
- if (!of_node) {
- pr_err("No Panel device present\n");
+ if (of_get_property(display->pdev->dev.of_node, "qcom,dsi-panel",
+ &size)) {
+ display->panel_count = size / sizeof(int);
+ display->panel_of = devm_kzalloc(&display->pdev->dev,
+ sizeof(struct device_node *) * display->panel_count,
+ GFP_KERNEL);
+ if (!display->panel_of) {
+ SDE_ERROR("out of memory for panel_of\n");
+ rc = -ENOMEM;
+ goto error;
+ }
+ display->panel = devm_kzalloc(&display->pdev->dev,
+ sizeof(struct dsi_panel *) * display->panel_count,
+ GFP_KERNEL);
+ if (!display->panel) {
+ SDE_ERROR("out of memory for panel\n");
+ rc = -ENOMEM;
+ goto error;
+ }
+ for (i = 0; i < display->panel_count; i++) {
+ display->panel_of[i] =
+ of_parse_phandle(display->pdev->dev.of_node,
+ "qcom,dsi-panel", i);
+ if (!display->panel_of[i]) {
+ SDE_ERROR("of_parse dsi-panel failed\n");
+ rc = -ENODEV;
+ goto error;
+ }
+ }
+ } else {
+ SDE_ERROR("No qcom,dsi-panel of node\n");
rc = -ENODEV;
goto error;
- } else {
- display->panel_of = of_node;
+ }
+
+ if (of_get_property(display->pdev->dev.of_node, "qcom,bridge-index",
+ &size)) {
+ if (size / sizeof(int) != display->panel_count) {
+ SDE_ERROR("size=%lu is different than count=%u\n",
+ size / sizeof(int), display->panel_count);
+ rc = -EINVAL;
+ goto error;
+ }
+ display->bridge_idx = devm_kzalloc(&display->pdev->dev,
+ sizeof(u32) * display->panel_count, GFP_KERNEL);
+ if (!display->bridge_idx) {
+ SDE_ERROR("out of memory for bridge_idx\n");
+ rc = -ENOMEM;
+ goto error;
+ }
+ for (i = 0; i < display->panel_count; i++) {
+ rc = of_property_read_u32_index(
+ display->pdev->dev.of_node,
+ "qcom,bridge-index", i,
+ &(display->bridge_idx[i]));
+ if (rc) {
+ SDE_ERROR(
+ "read bridge-index error,i=%d rc=%d\n",
+ i, rc);
+ rc = -ENODEV;
+ goto error;
+ }
+ }
}
rc = dsi_display_parse_lane_map(display);
@@ -1167,6 +1226,16 @@ static int dsi_display_parse_dt(struct dsi_display *display)
goto error;
}
error:
+ if (rc) {
+ if (display->panel_of)
+ for (i = 0; i < display->panel_count; i++)
+ if (display->panel_of[i])
+ of_node_put(display->panel_of[i]);
+ devm_kfree(&display->pdev->dev, display->panel_of);
+ devm_kfree(&display->pdev->dev, display->panel);
+ devm_kfree(&display->pdev->dev, display->bridge_idx);
+ display->panel_count = 0;
+ }
return rc;
}
@@ -1196,12 +1265,15 @@ static int dsi_display_res_init(struct dsi_display *display)
}
}
- display->panel = dsi_panel_get(&display->pdev->dev, display->panel_of);
- if (IS_ERR_OR_NULL(display->panel)) {
- rc = PTR_ERR(display->panel);
- pr_err("failed to get panel, rc=%d\n", rc);
- display->panel = NULL;
- goto error_ctrl_put;
+ for (i = 0; i < display->panel_count; i++) {
+ display->panel[i] = dsi_panel_get(&display->pdev->dev,
+ display->panel_of[i]);
+ if (IS_ERR_OR_NULL(display->panel)) {
+ rc = PTR_ERR(display->panel);
+ pr_err("failed to get panel, rc=%d\n", rc);
+ display->panel[i] = NULL;
+ goto error_ctrl_put;
+ }
}
rc = dsi_display_clocks_init(display);
@@ -1230,6 +1302,9 @@ static int dsi_display_res_deinit(struct dsi_display *display)
if (rc)
pr_err("clocks deinit failed, rc=%d\n", rc);
+ for (i = 0; i < display->panel_count; i++)
+ dsi_panel_put(display->panel[i]);
+
for (i = 0; i < display->ctrl_count; i++) {
ctrl = &display->ctrl[i];
dsi_phy_put(ctrl->phy);
@@ -1279,7 +1354,7 @@ static bool dsi_display_is_seamless_dfps_possible(
return false;
}
- cur = &display->panel->mode;
+ cur = &display->panel[0]->mode;
if (cur->timing.h_active != tgt->timing.h_active) {
pr_debug("timing.h_active differs %d %d\n",
@@ -1388,7 +1463,7 @@ static int dsi_display_dfps_update(struct dsi_display *display,
}
timing = &dsi_mode->timing;
- dsi_panel_get_dfps_caps(display->panel, &dfps_caps);
+ dsi_panel_get_dfps_caps(display->panel[0], &dfps_caps);
if (!dfps_caps.dfps_support) {
pr_err("dfps not supported\n");
return -ENOTSUPP;
@@ -1425,7 +1500,7 @@ static int dsi_display_dfps_update(struct dsi_display *display,
}
}
- panel_mode = &display->panel->mode;
+ panel_mode = &display->panel[0]->mode;
memcpy(panel_mode, dsi_mode, sizeof(*panel_mode));
error:
@@ -1493,7 +1568,8 @@ static int dsi_display_get_dfps_timing(struct dsi_display *display,
}
m_ctrl = display->ctrl[display->clk_master_idx].ctrl;
- dsi_panel_get_dfps_caps(display->panel, &dfps_caps);
+ /* Only check the first panel */
+ dsi_panel_get_dfps_caps(display->panel[0], &dfps_caps);
if (!dfps_caps.dfps_support) {
pr_err("dfps not supported by panel\n");
return -EINVAL;
@@ -1574,7 +1650,7 @@ static int dsi_display_set_mode_sub(struct dsi_display *display,
int i;
struct dsi_display_ctrl *ctrl;
- rc = dsi_panel_get_host_cfg_for_mode(display->panel,
+ rc = dsi_panel_get_host_cfg_for_mode(display->panel[0],
mode,
&display->config);
if (rc) {
@@ -1687,7 +1763,7 @@ static int dsi_display_bind(struct device *dev,
struct drm_device *drm;
struct dsi_display *display;
struct platform_device *pdev = to_platform_device(dev);
- int i, rc = 0;
+ int i, j, rc = 0;
if (!dev || !pdev || !master) {
pr_err("invalid param(s), dev %pK, pdev %pK, master %pK\n",
@@ -1737,15 +1813,19 @@ static int dsi_display_bind(struct device *dev,
goto error_ctrl_deinit;
}
- rc = dsi_panel_drv_init(display->panel, &display->host);
- if (rc) {
- if (rc != -EPROBE_DEFER)
- pr_err("[%s] failed to initialize panel driver, rc=%d\n",
- display->name, rc);
- goto error_host_deinit;
+ for (j = 0; j < display->panel_count; j++) {
+ rc = dsi_panel_drv_init(display->panel[j], &display->host);
+ if (rc) {
+ if (rc != -EPROBE_DEFER)
+ SDE_ERROR(
+ "[%s]Failed to init panel driver, rc=%d\n",
+ display->name, rc);
+ goto error_panel_deinit;
+ }
}
- rc = dsi_panel_get_mode_count(display->panel, &display->num_of_modes);
+ rc = dsi_panel_get_mode_count(display->panel[0],
+ &display->num_of_modes);
if (rc) {
pr_err("[%s] failed to get mode count, rc=%d\n",
display->name, rc);
@@ -1756,8 +1836,8 @@ static int dsi_display_bind(struct device *dev,
goto error;
error_panel_deinit:
- (void)dsi_panel_drv_deinit(display->panel);
-error_host_deinit:
+ for (j--; j >= 0; j--)
+ (void)dsi_panel_drv_deinit(display->panel[j]);
(void)dsi_display_mipi_host_deinit(display);
error_ctrl_deinit:
for (i = i - 1; i >= 0; i--) {
@@ -1798,10 +1878,12 @@ static void dsi_display_unbind(struct device *dev,
mutex_lock(&display->display_lock);
- rc = dsi_panel_drv_deinit(display->panel);
- if (rc)
- pr_err("[%s] failed to deinit panel driver, rc=%d\n",
- display->name, rc);
+ for (i = 0; i < display->panel_count; i++) {
+ rc = dsi_panel_drv_deinit(display->panel[i]);
+ if (rc)
+ SDE_ERROR("[%s] failed to deinit panel driver, rc=%d\n",
+ display->name, rc);
+ }
rc = dsi_display_mipi_host_deinit(display);
if (rc)
@@ -1870,7 +1952,7 @@ int dsi_display_dev_probe(struct platform_device *pdev)
display->pdev = pdev;
platform_set_drvdata(pdev, display);
mutex_lock(&dsi_display_list_lock);
- list_add(&display->list, &dsi_display_list);
+ list_add_tail(&display->list, &dsi_display_list);
mutex_unlock(&dsi_display_list_lock);
if (display->is_active) {
@@ -1890,7 +1972,7 @@ int dsi_display_dev_probe(struct platform_device *pdev)
int dsi_display_dev_remove(struct platform_device *pdev)
{
- int rc = 0;
+ int rc = 0, i;
struct dsi_display *display;
struct dsi_display *pos, *tmp;
@@ -1913,6 +1995,13 @@ int dsi_display_dev_remove(struct platform_device *pdev)
mutex_unlock(&dsi_display_list_lock);
platform_set_drvdata(pdev, NULL);
+ if (display->panel_of)
+ for (i = 0; i < display->panel_count; i++)
+ if (display->panel_of[i])
+ of_node_put(display->panel_of[i]);
+ devm_kfree(&pdev->dev, display->panel_of);
+ devm_kfree(&pdev->dev, display->panel);
+ devm_kfree(&pdev->dev, display->bridge_idx);
devm_kfree(&pdev->dev, display);
return rc;
}
@@ -1984,9 +2073,15 @@ void dsi_display_set_active_state(struct dsi_display *display, bool is_active)
int dsi_display_drm_bridge_init(struct dsi_display *display,
struct drm_encoder *enc)
{
- int rc = 0;
+ int rc = 0, i;
struct dsi_bridge *bridge;
+ struct drm_bridge *dba_bridge;
+ struct dba_bridge_init init_data;
+ struct drm_bridge *precede_bridge;
struct msm_drm_private *priv = NULL;
+ struct dsi_panel *panel;
+ u32 *bridge_idx;
+ u32 num_of_lanes = 0;
if (!display || !display->drm_dev || !enc) {
pr_err("invalid param(s)\n");
@@ -1997,44 +2092,112 @@ int dsi_display_drm_bridge_init(struct dsi_display *display,
priv = display->drm_dev->dev_private;
if (!priv) {
- pr_err("Private data is not present\n");
+ SDE_ERROR("Private data is not present\n");
rc = -EINVAL;
- goto error;
+ goto out;
}
if (display->bridge) {
- pr_err("display is already initialize\n");
- goto error;
+ SDE_ERROR("display is already initialize\n");
+ goto out;
}
bridge = dsi_drm_bridge_init(display, display->drm_dev, enc);
if (IS_ERR_OR_NULL(bridge)) {
rc = PTR_ERR(bridge);
- pr_err("[%s] brige init failed, %d\n", display->name, rc);
- goto error;
+ SDE_ERROR("[%s] brige init failed, %d\n", display->name, rc);
+ goto out;
}
display->bridge = bridge;
priv->bridges[priv->num_bridges++] = &bridge->base;
+ precede_bridge = &bridge->base;
+
+ if (display->panel_count >= MAX_BRIDGES - 1) {
+ SDE_ERROR("too many bridge chips=%d\n", display->panel_count);
+ goto error_bridge;
+ }
+
+ for (i = 0; i < display->panel_count; i++) {
+ panel = display->panel[i];
+ if (panel && display->bridge_idx &&
+ panel->dba_config.dba_panel) {
+ bridge_idx = display->bridge_idx + i;
+ num_of_lanes = 0;
+ memset(&init_data, 0x00, sizeof(init_data));
+ if (panel->host_config.data_lanes & DSI_DATA_LANE_0)
+ num_of_lanes++;
+ if (panel->host_config.data_lanes & DSI_DATA_LANE_1)
+ num_of_lanes++;
+ if (panel->host_config.data_lanes & DSI_DATA_LANE_2)
+ num_of_lanes++;
+ if (panel->host_config.data_lanes & DSI_DATA_LANE_3)
+ num_of_lanes++;
+ init_data.client_name = DSI_DBA_CLIENT_NAME;
+ init_data.chip_name = panel->dba_config.bridge_name;
+ init_data.id = *bridge_idx;
+ init_data.display = display;
+ init_data.hdmi_mode = panel->dba_config.hdmi_mode;
+ init_data.num_of_input_lanes = num_of_lanes;
+ init_data.precede_bridge = precede_bridge;
+ init_data.panel_count = display->panel_count;
+ dba_bridge = dba_bridge_init(display->drm_dev, enc,
+ &init_data);
+ if (IS_ERR_OR_NULL(dba_bridge)) {
+ rc = PTR_ERR(dba_bridge);
+ SDE_ERROR("[%s:%d] dba brige init failed, %d\n",
+ init_data.chip_name, init_data.id, rc);
+ goto error_dba_bridge;
+ }
+ priv->bridges[priv->num_bridges++] = dba_bridge;
+ precede_bridge = dba_bridge;
+ }
+ }
-error:
+ goto out;
+
+error_dba_bridge:
+ for (i = 1; i < MAX_BRIDGES; i++) {
+ dba_bridge_cleanup(priv->bridges[i]);
+ priv->bridges[i] = NULL;
+ }
+error_bridge:
+ dsi_drm_bridge_cleanup(display->bridge);
+ display->bridge = NULL;
+ priv->bridges[0] = NULL;
+ priv->num_bridges = 0;
+out:
mutex_unlock(&display->display_lock);
return rc;
}
int dsi_display_drm_bridge_deinit(struct dsi_display *display)
{
- int rc = 0;
+ int rc = 0, i;
+ struct msm_drm_private *priv = NULL;
if (!display) {
- pr_err("Invalid params\n");
+ SDE_ERROR("Invalid params\n");
+ return -EINVAL;
+ }
+ priv = display->drm_dev->dev_private;
+
+ if (!priv) {
+ SDE_ERROR("Private data is not present\n");
return -EINVAL;
}
mutex_lock(&display->display_lock);
+ for (i = 1; i < MAX_BRIDGES; i++) {
+ dba_bridge_cleanup(priv->bridges[i]);
+ priv->bridges[i] = NULL;
+ }
+
dsi_drm_bridge_cleanup(display->bridge);
display->bridge = NULL;
+ priv->bridges[0] = NULL;
+ priv->num_bridges = 0;
mutex_unlock(&display->display_lock);
return rc;
@@ -2053,7 +2216,7 @@ int dsi_display_get_info(struct msm_display_info *info, void *disp)
display = disp;
mutex_lock(&display->display_lock);
- rc = dsi_panel_get_phy_props(display->panel, &phy_props);
+ rc = dsi_panel_get_phy_props(display->panel[0], &phy_props);
if (rc) {
pr_err("[%s] failed to get panel phy props, rc=%d\n",
display->name, rc);
@@ -2073,7 +2236,7 @@ int dsi_display_get_info(struct msm_display_info *info, void *disp)
info->max_height = 1080;
info->compression = MSM_DISPLAY_COMPRESS_NONE;
- switch (display->panel->mode.panel_mode) {
+ switch (display->panel[0]->mode.panel_mode) {
case DSI_OP_VIDEO_MODE:
info->capabilities |= MSM_DISPLAY_CAP_VID_MODE;
break;
@@ -2082,7 +2245,7 @@ int dsi_display_get_info(struct msm_display_info *info, void *disp)
break;
default:
pr_err("unknwown dsi panel mode %d\n",
- display->panel->mode.panel_mode);
+ display->panel[0]->mode.panel_mode);
break;
}
error:
@@ -2106,7 +2269,7 @@ int dsi_display_get_modes(struct dsi_display *display,
mutex_lock(&display->display_lock);
- rc = dsi_panel_get_dfps_caps(display->panel, &dfps_caps);
+ rc = dsi_panel_get_dfps_caps(display->panel[0], &dfps_caps);
if (rc) {
pr_err("[%s] failed to get dfps caps from panel\n",
display->name);
@@ -2127,7 +2290,8 @@ int dsi_display_get_modes(struct dsi_display *display,
/* Insert the dfps "sub-modes" between main panel modes */
int panel_mode_idx = i / num_dfps_rates;
- rc = dsi_panel_get_mode(display->panel, panel_mode_idx, modes);
+ rc = dsi_panel_get_mode(display->panel[0], panel_mode_idx,
+ modes);
if (rc) {
pr_err("[%s] failed to get mode from panel\n",
display->name);
@@ -2178,7 +2342,7 @@ int dsi_display_validate_mode(struct dsi_display *display,
adj_mode = *mode;
adjust_timing_by_ctrl_count(display, &adj_mode);
- rc = dsi_panel_validate_mode(display->panel, &adj_mode);
+ rc = dsi_panel_validate_mode(display->panel[0], &adj_mode);
if (rc) {
pr_err("[%s] panel mode validation failed, rc=%d\n",
display->name, rc);
@@ -2278,7 +2442,7 @@ error:
int dsi_display_prepare(struct dsi_display *display)
{
- int rc = 0;
+ int rc = 0, i, j;
if (!display) {
pr_err("Invalid params\n");
@@ -2287,11 +2451,13 @@ int dsi_display_prepare(struct dsi_display *display)
mutex_lock(&display->display_lock);
- rc = dsi_panel_pre_prepare(display->panel);
- if (rc) {
- pr_err("[%s] panel pre-prepare failed, rc=%d\n",
- display->name, rc);
- goto error;
+ for (i = 0; i < display->panel_count; i++) {
+ rc = dsi_panel_pre_prepare(display->panel[i]);
+ if (rc) {
+ SDE_ERROR("[%s] panel pre-prepare failed, rc=%d\n",
+ display->name, rc);
+ goto error_panel_post_unprep;
+ }
}
rc = dsi_display_ctrl_power_on(display);
@@ -2349,15 +2515,20 @@ int dsi_display_prepare(struct dsi_display *display)
goto error_ctrl_link_off;
}
- rc = dsi_panel_prepare(display->panel);
- if (rc) {
- pr_err("[%s] panel prepare failed, rc=%d\n", display->name, rc);
- goto error_host_engine_off;
+ for (j = 0; j < display->panel_count; j++) {
+ rc = dsi_panel_prepare(display->panel[j]);
+ if (rc) {
+ SDE_ERROR("[%s] panel prepare failed, rc=%d\n",
+ display->name, rc);
+ goto error_panel_unprep;
+ }
}
goto error;
-error_host_engine_off:
+error_panel_unprep:
+ for (j--; j >= 0; j--)
+ (void)dsi_panel_unprepare(display->panel[j]);
(void)dsi_display_ctrl_host_disable(display);
error_ctrl_link_off:
(void)dsi_display_ctrl_link_clk_off(display);
@@ -2372,7 +2543,8 @@ error_phy_pwr_off:
error_ctrl_pwr_off:
(void)dsi_display_ctrl_power_off(display);
error_panel_post_unprep:
- (void)dsi_panel_post_unprepare(display->panel);
+ for (i--; i >= 0; i--)
+ (void)dsi_panel_post_unprepare(display->panel[i]);
error:
mutex_unlock(&display->display_lock);
return rc;
@@ -2380,7 +2552,7 @@ error:
int dsi_display_enable(struct dsi_display *display)
{
- int rc = 0;
+ int rc = 0, i;
if (!display) {
pr_err("Invalid params\n");
@@ -2389,11 +2561,13 @@ int dsi_display_enable(struct dsi_display *display)
mutex_lock(&display->display_lock);
- rc = dsi_panel_enable(display->panel);
- if (rc) {
- pr_err("[%s] failed to enable DSI panel, rc=%d\n",
- display->name, rc);
- goto error;
+ for (i = 0; i < display->panel_count; i++) {
+ rc = dsi_panel_enable(display->panel[i]);
+ if (rc) {
+ SDE_ERROR("[%s] failed to enable DSI panel, rc=%d\n",
+ display->name, rc);
+ goto error_disable_panel;
+ }
}
if (display->config.panel_mode == DSI_OP_VIDEO_MODE) {
@@ -2419,7 +2593,8 @@ int dsi_display_enable(struct dsi_display *display)
goto error;
error_disable_panel:
- (void)dsi_panel_disable(display->panel);
+ for (i--; i >= 0; i--)
+ (void)dsi_panel_disable(display->panel[i]);
error:
mutex_unlock(&display->display_lock);
return rc;
@@ -2427,7 +2602,7 @@ error:
int dsi_display_post_enable(struct dsi_display *display)
{
- int rc = 0;
+ int rc = 0, i;
if (!display) {
pr_err("Invalid params\n");
@@ -2436,10 +2611,12 @@ int dsi_display_post_enable(struct dsi_display *display)
mutex_lock(&display->display_lock);
- rc = dsi_panel_post_enable(display->panel);
- if (rc)
- pr_err("[%s] panel post-enable failed, rc=%d\n",
- display->name, rc);
+ for (i = 0; i < display->panel_count; i++) {
+ rc = dsi_panel_post_enable(display->panel[i]);
+ if (rc)
+ SDE_ERROR("[%s] panel post-enable failed, rc=%d\n",
+ display->name, rc);
+ }
mutex_unlock(&display->display_lock);
return rc;
@@ -2447,7 +2624,7 @@ int dsi_display_post_enable(struct dsi_display *display)
int dsi_display_pre_disable(struct dsi_display *display)
{
- int rc = 0;
+ int rc = 0, i;
if (!display) {
pr_err("Invalid params\n");
@@ -2456,10 +2633,12 @@ int dsi_display_pre_disable(struct dsi_display *display)
mutex_lock(&display->display_lock);
- rc = dsi_panel_pre_disable(display->panel);
- if (rc)
- pr_err("[%s] panel pre-disable failed, rc=%d\n",
- display->name, rc);
+ for (i = 0; i < display->panel_count; i++) {
+ rc = dsi_panel_pre_disable(display->panel[i]);
+ if (rc)
+ SDE_ERROR("[%s] panel pre-disable failed, rc=%d\n",
+ display->name, rc);
+ }
mutex_unlock(&display->display_lock);
return rc;
@@ -2467,7 +2646,7 @@ int dsi_display_pre_disable(struct dsi_display *display)
int dsi_display_disable(struct dsi_display *display)
{
- int rc = 0;
+ int rc = 0, i;
if (!display) {
pr_err("Invalid params\n");
@@ -2481,10 +2660,12 @@ int dsi_display_disable(struct dsi_display *display)
pr_err("[%s] display wake up failed, rc=%d\n",
display->name, rc);
- rc = dsi_panel_disable(display->panel);
- if (rc)
- pr_err("[%s] failed to disable DSI panel, rc=%d\n",
- display->name, rc);
+ for (i = 0; i < display->panel_count; i++) {
+ rc = dsi_panel_disable(display->panel[i]);
+ if (rc)
+ SDE_ERROR("[%s] failed to disable DSI panel, rc=%d\n",
+ display->name, rc);
+ }
if (display->config.panel_mode == DSI_OP_VIDEO_MODE) {
rc = dsi_display_vid_engine_disable(display);
@@ -2507,7 +2688,7 @@ int dsi_display_disable(struct dsi_display *display)
int dsi_display_unprepare(struct dsi_display *display)
{
- int rc = 0;
+ int rc = 0, i;
if (!display) {
pr_err("Invalid params\n");
@@ -2521,10 +2702,12 @@ int dsi_display_unprepare(struct dsi_display *display)
pr_err("[%s] display wake up failed, rc=%d\n",
display->name, rc);
- rc = dsi_panel_unprepare(display->panel);
- if (rc)
- pr_err("[%s] panel unprepare failed, rc=%d\n",
- display->name, rc);
+ for (i = 0; i < display->panel_count; i++) {
+ rc = dsi_panel_unprepare(display->panel[i]);
+ if (rc)
+ SDE_ERROR("[%s] panel unprepare failed, rc=%d\n",
+ display->name, rc);
+ }
rc = dsi_display_ctrl_host_disable(display);
if (rc)
@@ -2561,10 +2744,12 @@ int dsi_display_unprepare(struct dsi_display *display)
pr_err("[%s] failed to power DSI vregs, rc=%d\n",
display->name, rc);
- rc = dsi_panel_post_unprepare(display->panel);
- if (rc)
- pr_err("[%s] panel post-unprepare failed, rc=%d\n",
- display->name, rc);
+ for (i = 0; i < display->panel_count; i++) {
+ rc = dsi_panel_post_unprepare(display->panel[i]);
+ if (rc)
+ pr_err("[%s] panel post-unprepare failed, rc=%d\n",
+ display->name, rc);
+ }
mutex_unlock(&display->display_lock);
return rc;
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_display.h b/drivers/gpu/drm/msm/dsi-staging/dsi_display.h
index b77bf268dbd1..210b8d00850b 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_display.h
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_display.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2015-2017, 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
@@ -102,8 +102,11 @@ struct dsi_display_clk_info {
* @display_lock: Mutex for dsi_display interface.
* @ctrl_count: Number of DSI interfaces required by panel.
* @ctrl: Controller information for DSI display.
+ * @panel_count: Number of DSI panel.
* @panel: Handle to DSI panel.
- * @panel_of: pHandle to DSI panel.
+ * @panel_of: pHandle to DSI panel, it's an array with panel_count
+ * of struct device_node pointers.
+ * @bridge_idx: Bridge chip index for each panel_of.
* @type: DSI display type.
* @clk_master_idx: The master controller for controlling clocks. This is an
* index into the ctrl[MAX_DSI_CTRLS_PER_DISPLAY] array.
@@ -133,8 +136,10 @@ struct dsi_display {
struct dsi_display_ctrl ctrl[MAX_DSI_CTRLS_PER_DISPLAY];
/* panel info */
- struct dsi_panel *panel;
- struct device_node *panel_of;
+ u32 panel_count;
+ struct dsi_panel **panel;
+ struct device_node **panel_of;
+ u32 *bridge_idx;
enum dsi_display_type type;
u32 clk_master_idx;
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c
index a1adecf81cc0..995cda97a2f0 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016-2017, 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
@@ -213,18 +213,21 @@ static void dsi_bridge_mode_set(struct drm_bridge *bridge,
struct drm_display_mode *adjusted_mode)
{
struct dsi_bridge *c_bridge = to_dsi_bridge(bridge);
+ struct dsi_panel *panel;
- if (!bridge || !mode || !adjusted_mode) {
+ if (!bridge || !mode || !adjusted_mode || !c_bridge->display ||
+ !c_bridge->display->panel[0]) {
pr_err("Invalid params\n");
return;
}
+ /* dsi drm bridge is always the first panel */
+ panel = c_bridge->display->panel[0];
memset(&(c_bridge->dsi_mode), 0x0, sizeof(struct dsi_display_mode));
convert_to_dsi_mode(adjusted_mode, &(c_bridge->dsi_mode));
pr_debug("note: using panel cmd/vid mode instead of user val\n");
- c_bridge->dsi_mode.panel_mode =
- c_bridge->display->panel->mode.panel_mode;
+ c_bridge->dsi_mode.panel_mode = panel->mode.panel_mode;
}
static bool dsi_bridge_mode_fixup(struct drm_bridge *bridge,
@@ -271,6 +274,7 @@ int dsi_conn_post_init(struct drm_connector *connector,
{
struct dsi_display *dsi_display = display;
struct dsi_panel *panel;
+ int i;
if (!info || !dsi_display)
return -EINVAL;
@@ -299,60 +303,65 @@ int dsi_conn_post_init(struct drm_connector *connector,
break;
}
- if (!dsi_display->panel) {
- pr_debug("invalid panel data\n");
- goto end;
- }
-
- panel = dsi_display->panel;
- sde_kms_info_add_keystr(info, "panel name", panel->name);
-
- switch (panel->mode.panel_mode) {
- case DSI_OP_VIDEO_MODE:
- sde_kms_info_add_keystr(info, "panel mode", "video");
- break;
- case DSI_OP_CMD_MODE:
- sde_kms_info_add_keystr(info, "panel mode", "command");
- sde_kms_info_add_keyint(info, "mdp_transfer_time_us",
- panel->cmd_config.mdp_transfer_time_us);
- break;
- default:
- pr_debug("invalid panel type:%d\n", panel->mode.panel_mode);
- break;
- }
- sde_kms_info_add_keystr(info, "dfps support",
- panel->dfps_caps.dfps_support ? "true" : "false");
+ for (i = 0; i < dsi_display->panel_count; i++) {
+ if (!dsi_display->panel[i]) {
+ pr_debug("invalid panel data\n");
+ goto end;
+ }
- switch (panel->phy_props.rotation) {
- case DSI_PANEL_ROTATE_NONE:
- sde_kms_info_add_keystr(info, "panel orientation", "none");
- break;
- case DSI_PANEL_ROTATE_H_FLIP:
- sde_kms_info_add_keystr(info, "panel orientation", "horz flip");
- break;
- case DSI_PANEL_ROTATE_V_FLIP:
- sde_kms_info_add_keystr(info, "panel orientation", "vert flip");
- break;
- default:
- pr_debug("invalid panel rotation:%d\n",
+ panel = dsi_display->panel[i];
+ sde_kms_info_add_keystr(info, "panel name", panel->name);
+
+ switch (panel->mode.panel_mode) {
+ case DSI_OP_VIDEO_MODE:
+ sde_kms_info_add_keystr(info, "panel mode", "video");
+ break;
+ case DSI_OP_CMD_MODE:
+ sde_kms_info_add_keystr(info, "panel mode", "command");
+ break;
+ default:
+ pr_debug("invalid panel type:%d\n",
+ panel->mode.panel_mode);
+ break;
+ }
+ sde_kms_info_add_keystr(info, "dfps support",
+ panel->dfps_caps.dfps_support ?
+ "true" : "false");
+
+ switch (panel->phy_props.rotation) {
+ case DSI_PANEL_ROTATE_NONE:
+ sde_kms_info_add_keystr(info, "panel orientation",
+ "none");
+ break;
+ case DSI_PANEL_ROTATE_H_FLIP:
+ sde_kms_info_add_keystr(info, "panel orientation",
+ "horz flip");
+ break;
+ case DSI_PANEL_ROTATE_V_FLIP:
+ sde_kms_info_add_keystr(info, "panel orientation",
+ "vert flip");
+ break;
+ default:
+ pr_debug("invalid panel rotation:%d\n",
panel->phy_props.rotation);
- break;
- }
+ break;
+ }
- switch (panel->bl_config.type) {
- case DSI_BACKLIGHT_PWM:
- sde_kms_info_add_keystr(info, "backlight type", "pwm");
- break;
- case DSI_BACKLIGHT_WLED:
- sde_kms_info_add_keystr(info, "backlight type", "wled");
- break;
- case DSI_BACKLIGHT_DCS:
- sde_kms_info_add_keystr(info, "backlight type", "dcs");
- break;
- default:
- pr_debug("invalid panel backlight type:%d\n",
- panel->bl_config.type);
- break;
+ switch (panel->bl_config.type) {
+ case DSI_BACKLIGHT_PWM:
+ sde_kms_info_add_keystr(info, "backlight type", "pwm");
+ break;
+ case DSI_BACKLIGHT_WLED:
+ sde_kms_info_add_keystr(info, "backlight type", "wled");
+ break;
+ case DSI_BACKLIGHT_DCS:
+ sde_kms_info_add_keystr(info, "backlight type", "dcs");
+ break;
+ default:
+ pr_debug("invalid panel backlight type:%d\n",
+ panel->bl_config.type);
+ break;
+ }
}
end:
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_panel.c b/drivers/gpu/drm/msm/dsi-staging/dsi_panel.c
index a7a39e685d4d..b1319a68429f 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_panel.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_panel.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016-2017, 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
@@ -17,6 +17,7 @@
#include <linux/gpio.h>
#include <linux/of_gpio.h>
+#include "sde_kms.h"
#include "dsi_panel.h"
#include "dsi_ctrl_hw.h"
@@ -171,10 +172,12 @@ static int dsi_panel_set_pinctrl_state(struct dsi_panel *panel, bool enable)
else
state = panel->pinctrl.suspend;
- rc = pinctrl_select_state(panel->pinctrl.pinctrl, state);
- if (rc)
- pr_err("[%s] failed to set pin state, rc=%d\n", panel->name,
- rc);
+ if (panel->pinctrl.pinctrl && state) {
+ rc = pinctrl_select_state(panel->pinctrl.pinctrl, state);
+ if (rc)
+ pr_err("[%s] failed to set pin state, rc=%d\n",
+ panel->name, rc);
+ }
return rc;
}
@@ -386,6 +389,9 @@ static int dsi_panel_bl_register(struct dsi_panel *panel)
case DSI_BACKLIGHT_WLED:
rc = dsi_panel_led_bl_register(panel, bl);
break;
+ case DSI_BACKLIGHT_UNKNOWN:
+ DRM_INFO("backlight type is unknown\n");
+ break;
default:
pr_err("Backlight type(%d) not supported\n", bl->type);
rc = -ENOTSUPP;
@@ -704,6 +710,8 @@ static int dsi_panel_parse_misc_host_config(struct dsi_host_common_cfg *host,
host->append_tx_eot = of_property_read_bool(of_node,
"qcom,mdss-dsi-tx-eot-append");
+ host->force_clk_lane_hs = of_property_read_bool(of_node,
+ "qcom,mdss-dsi-force-clock-lane-hs");
return 0;
}
@@ -1348,6 +1356,8 @@ static int dsi_panel_parse_gpios(struct dsi_panel *panel,
{
int rc = 0;
+ /* Need to set GPIO default value to -1, since 0 is a valid value */
+ panel->reset_config.disp_en_gpio = -1;
panel->reset_config.reset_gpio = of_get_named_gpio(of_node,
"qcom,platform-reset-gpio",
0);
@@ -1496,6 +1506,33 @@ error:
return rc;
}
+static int dsi_panel_parse_dba_config(struct dsi_panel *panel,
+ struct device_node *of_node)
+{
+ int rc = 0, len = 0;
+
+ panel->dba_config.dba_panel = of_property_read_bool(of_node,
+ "qcom,dba-panel");
+
+ if (panel->dba_config.dba_panel) {
+ panel->dba_config.hdmi_mode = of_property_read_bool(of_node,
+ "qcom,hdmi-mode");
+
+ panel->dba_config.bridge_name = of_get_property(of_node,
+ "qcom,bridge-name", &len);
+ if (!panel->dba_config.bridge_name || len <= 0) {
+ SDE_ERROR(
+ "%s:%d Unable to read bridge_name, data=%pK,len=%d\n",
+ __func__, __LINE__, panel->dba_config.bridge_name, len);
+ rc = -EINVAL;
+ goto error;
+ }
+ }
+
+error:
+ return rc;
+}
+
struct dsi_panel *dsi_panel_get(struct device *parent,
struct device_node *of_node)
{
@@ -1560,6 +1597,10 @@ struct dsi_panel *dsi_panel_get(struct device *parent,
if (rc)
pr_err("failed to parse backlight config, rc=%d\n", rc);
+ rc = dsi_panel_parse_dba_config(panel, of_node);
+ if (rc)
+ pr_err("failed to parse dba config, rc=%d\n", rc);
+
panel->panel_of_node = of_node;
drm_panel_init(&panel->drm_panel);
mutex_init(&panel->panel_lock);
@@ -1574,6 +1615,9 @@ void dsi_panel_put(struct dsi_panel *panel)
{
u32 i;
+ if (!panel)
+ return;
+
for (i = 0; i < DSI_CMD_SET_MAX; i++)
dsi_panel_destroy_cmd_packets(&panel->cmd_sets[i]);
@@ -1614,10 +1658,8 @@ int dsi_panel_drv_init(struct dsi_panel *panel,
}
rc = dsi_panel_pinctrl_init(panel);
- if (rc) {
+ if (rc)
pr_err("[%s] failed to init pinctrl, rc=%d\n", panel->name, rc);
- goto error_vreg_put;
- }
rc = dsi_panel_gpio_request(panel);
if (rc) {
@@ -1640,7 +1682,6 @@ error_gpio_release:
(void)dsi_panel_gpio_release(panel);
error_pinctrl_deinit:
(void)dsi_panel_pinctrl_deinit(panel);
-error_vreg_put:
(void)dsi_panel_vreg_put(panel);
exit:
mutex_unlock(&panel->panel_lock);
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_panel.h b/drivers/gpu/drm/msm/dsi-staging/dsi_panel.h
index 4d21a4cf6428..8106ed1261b4 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_panel.h
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_panel.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016-2017, 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
@@ -132,6 +132,18 @@ struct dsi_panel_reset_config {
int disp_en_gpio;
};
+/**
+ * struct dsi_panel_dba - DSI DBA panel information
+ * @dba_panel: Indicate if it's DBA panel
+ * @bridge_name: Bridge chip name
+ * @hdmi_mode: If bridge chip is in hdmi mode.
+ */
+struct dsi_panel_dba {
+ bool dba_panel;
+ const char *bridge_name;
+ bool hdmi_mode;
+};
+
struct dsi_panel {
const char *name;
struct device_node *panel_of_node;
@@ -158,6 +170,8 @@ struct dsi_panel {
struct dsi_panel_reset_config reset_config;
struct dsi_pinctrl_info pinctrl;
+ struct dsi_panel_dba dba_config;
+
bool lp11_init;
};
diff --git a/drivers/gpu/drm/msm/msm_smmu.c b/drivers/gpu/drm/msm/msm_smmu.c
index c99f51e09700..b22eb420c3e3 100644
--- a/drivers/gpu/drm/msm/msm_smmu.c
+++ b/drivers/gpu/drm/msm/msm_smmu.c
@@ -53,6 +53,17 @@ struct msm_smmu_domain {
#define to_msm_smmu(x) container_of(x, struct msm_smmu, base)
#define msm_smmu_to_client(smmu) (smmu->client)
+
+static int msm_smmu_fault_handler(struct iommu_domain *iommu,
+ struct device *dev, unsigned long iova, int flags, void *arg)
+{
+
+ dev_info(dev, "%s: iova=0x%08lx, flags=0x%x, iommu=%pK\n", __func__,
+ iova, flags, iommu);
+ return 0;
+}
+
+
static int _msm_smmu_create_mapping(struct msm_smmu_client *client,
const struct msm_smmu_domain *domain);
@@ -362,6 +373,7 @@ struct msm_mmu *msm_smmu_new(struct device *dev,
{
struct msm_smmu *smmu;
struct device *client_dev;
+ struct msm_smmu_client *client;
smmu = kzalloc(sizeof(*smmu), GFP_KERNEL);
if (!smmu)
@@ -376,6 +388,11 @@ struct msm_mmu *msm_smmu_new(struct device *dev,
smmu->client_dev = client_dev;
msm_mmu_init(&smmu->base, dev, &funcs);
+ client = msm_smmu_to_client(smmu);
+ if (client)
+ iommu_set_fault_handler(client->mmu_mapping->domain,
+ msm_smmu_fault_handler, dev);
+
return &smmu->base;
}
diff --git a/drivers/gpu/drm/msm/sde/sde_backlight.c b/drivers/gpu/drm/msm/sde/sde_backlight.c
index 9034eeb944fe..78df28a0016b 100644
--- a/drivers/gpu/drm/msm/sde/sde_backlight.c
+++ b/drivers/gpu/drm/msm/sde/sde_backlight.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2016-2017, 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
@@ -37,15 +37,15 @@ static int sde_backlight_device_update_status(struct backlight_device *bd)
connector = bl_get_data(bd);
c_conn = to_sde_connector(connector);
display = (struct dsi_display *) c_conn->display;
- if (brightness > display->panel->bl_config.bl_max_level)
- brightness = display->panel->bl_config.bl_max_level;
+ if (brightness > display->panel[0]->bl_config.bl_max_level)
+ brightness = display->panel[0]->bl_config.bl_max_level;
/* This maps UI brightness into driver backlight level with
* rounding
*/
SDE_BRIGHT_TO_BL(bl_lvl, brightness,
- display->panel->bl_config.bl_max_level,
- display->panel->bl_config.brightness_max_level);
+ display->panel[0]->bl_config.bl_max_level,
+ display->panel[0]->bl_config.brightness_max_level);
if (!bl_lvl && brightness)
bl_lvl = 1;
@@ -85,7 +85,7 @@ int sde_backlight_setup(struct drm_connector *connector)
switch (c_conn->connector_type) {
case DRM_MODE_CONNECTOR_DSI:
display = (struct dsi_display *) c_conn->display;
- bl_config = &display->panel->bl_config;
+ bl_config = &display->panel[0]->bl_config;
props.max_brightness = bl_config->brightness_max_level;
props.brightness = bl_config->brightness_max_level;
bd = backlight_device_register("sde-backlight",
diff --git a/drivers/gpu/drm/msm/sde/sde_connector.c b/drivers/gpu/drm/msm/sde/sde_connector.c
index 31cf25ab5691..12546c059f6f 100644
--- a/drivers/gpu/drm/msm/sde/sde_connector.c
+++ b/drivers/gpu/drm/msm/sde/sde_connector.c
@@ -540,14 +540,6 @@ struct drm_connector *sde_connector_init(struct drm_device *dev,
goto error_unregister_conn;
}
- if (c_conn->ops.set_backlight) {
- rc = sde_backlight_setup(&c_conn->base);
- if (rc) {
- pr_err("failed to setup backlight, rc=%d\n", rc);
- goto error_unregister_conn;
- }
- }
-
/* create properties */
msm_property_init(&c_conn->property_info, &c_conn->base.base, dev,
priv->conn_property, c_conn->property_data,
diff --git a/drivers/gpu/drm/msm/sde/sde_formats.c b/drivers/gpu/drm/msm/sde/sde_formats.c
index 23ffa9b554dd..a59ec31ba276 100644
--- a/drivers/gpu/drm/msm/sde/sde_formats.c
+++ b/drivers/gpu/drm/msm/sde/sde_formats.c
@@ -718,12 +718,12 @@ static int _sde_format_populate_addrs_linear(
{
unsigned int i;
- /* Can now check the pitches given vs pitches expected */
+ /* Update layout pitches from fb */
for (i = 0; i < layout->num_planes; ++i) {
if (layout->plane_pitch[i] != fb->pitches[i]) {
- DRM_ERROR("plane %u expected pitch %u, fb %u\n",
+ SDE_DEBUG("plane %u expected pitch %u, fb %u\n",
i, layout->plane_pitch[i], fb->pitches[i]);
- return -EINVAL;
+ layout->plane_pitch[i] = fb->pitches[i];
}
}
diff --git a/drivers/gpu/drm/msm/sde/sde_kms.c b/drivers/gpu/drm/msm/sde/sde_kms.c
index 581918da183f..c92a61c06472 100644
--- a/drivers/gpu/drm/msm/sde/sde_kms.c
+++ b/drivers/gpu/drm/msm/sde/sde_kms.c
@@ -416,6 +416,10 @@ static void sde_kms_wait_for_commit_done(struct msm_kms *kms,
return;
}
+ ret = drm_crtc_vblank_get(crtc);
+ if (ret)
+ return;
+
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
if (encoder->crtc != crtc)
continue;
@@ -431,6 +435,8 @@ static void sde_kms_wait_for_commit_done(struct msm_kms *kms,
break;
}
}
+
+ drm_crtc_vblank_put(crtc);
}
static void sde_kms_prepare_fence(struct msm_kms *kms,
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index f64111886584..7a504b1ad94d 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -41,7 +41,7 @@ config IOMMU_IO_PGTABLE_LPAE_SELFTEST
config IOMMU_IO_PGTABLE_FAST
bool "Fast ARMv7/v8 Long Descriptor Format"
- select IOMMU_IO_PGTABLE
+ depends on ARM64_DMA_USE_IOMMU
help
Enable support for a subset of the ARM long descriptor pagetable
format. This allocator achieves fast performance by
diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c
index 7922608287d7..7f39ea93e110 100644
--- a/drivers/iommu/arm-smmu.c
+++ b/drivers/iommu/arm-smmu.c
@@ -2190,8 +2190,8 @@ static int arm_smmu_attach_dynamic(struct iommu_domain *domain,
smmu->num_context_banks + 2,
MAX_ASID + 1, GFP_KERNEL);
if (ret < 0) {
- dev_err(smmu->dev, "dynamic ASID allocation failed: %d\n",
- ret);
+ dev_err_ratelimited(smmu->dev,
+ "dynamic ASID allocation failed: %d\n", ret);
goto out;
}
diff --git a/drivers/iommu/iommu-debug.c b/drivers/iommu/iommu-debug.c
index ffeb47c6b367..28a817aba3fc 100644
--- a/drivers/iommu/iommu-debug.c
+++ b/drivers/iommu/iommu-debug.c
@@ -1673,10 +1673,14 @@ static ssize_t iommu_debug_atos_read(struct file *file, char __user *ubuf,
memset(buf, 0, 100);
phys = iommu_iova_to_phys_hard(ddev->domain, ddev->iova);
- if (!phys)
+ if (!phys) {
strlcpy(buf, "FAIL\n", 100);
- else
+ phys = iommu_iova_to_phys(ddev->domain, ddev->iova);
+ dev_err(ddev->dev, "ATOS for %pa failed. Software walk returned: %pa\n",
+ &ddev->iova, &phys);
+ } else {
snprintf(buf, 100, "%pa\n", &phys);
+ }
buflen = strlen(buf);
if (copy_to_user(ubuf, buf, buflen)) {
diff --git a/drivers/net/can/spi/rh850.c b/drivers/net/can/spi/rh850.c
index c7a2182003bf..a93f979da9ed 100644
--- a/drivers/net/can/spi/rh850.c
+++ b/drivers/net/can/spi/rh850.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2015-2017, 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
@@ -18,6 +18,7 @@
#include <linux/spi/spi.h>
#include <linux/can.h>
#include <linux/can/dev.h>
+#include <linux/completion.h>
#define DEBUG_RH850 0
#if DEBUG_RH850 == 1
@@ -33,8 +34,11 @@
#define MAX_TX_BUFFERS 1
#define XFER_BUFFER_SIZE 64
#define RX_ASSEMBLY_BUFFER_SIZE 128
-#define RH850_CLOCK 80000000
+#define RH850_CLOCK 16000000
#define RH850_MAX_CHANNELS 4
+#define DRIVER_MODE_RAW_FRAMES 0
+#define DRIVER_MODE_PROPERTIES 1
+#define DRIVER_MODE_AMB 2
struct rh850_can {
struct net_device *netdev[RH850_MAX_CHANNELS];
@@ -50,6 +54,10 @@ struct rh850_can {
char *assembly_buffer;
u8 assembly_buffer_size;
atomic_t netif_queue_stop;
+ struct completion response_completion;
+ int wait_cmd;
+ int cmd_result;
+ int driver_mode;
};
struct rh850_netdev_privdata {
@@ -84,6 +92,36 @@ struct spi_miso { /* TLV for MISO line */
#define CMD_CAN_ADD_FILTER 0x83
#define CMD_CAN_REMOVE_FILTER 0x84
#define CMD_CAN_RECEIVE_FRAME 0x85
+#define CMD_CAN_CONFIG_BIT_TIMING 0x86
+
+#define CMD_CAN_DATA_BUFF_ADD 0x87
+#define CMD_CAN_DATA_BUFF_REMOVE 0X88
+#define CMD_CAN_RELEASE_BUFFER 0x89
+#define CMD_CAN_DATA_BUFF_REMOVE_ALL 0x8A
+#define CMD_PROPERTY_WRITE 0x8B
+#define CMD_PROPERTY_READ 0x8C
+
+#define CMD_GET_FW_BR_VERSION 0x95
+#define CMD_BEGIN_FIRMWARE_UPGRADE 0x96
+#define CMD_FIRMWARE_UPGRADE_DATA 0x97
+#define CMD_END_FIRMWARE_UPGRADE 0x98
+#define CMD_BEGIN_BOOT_ROM_UPGRADE 0x99
+#define CMD_BOOT_ROM_UPGRADE_DATA 0x9A
+#define CMD_END_BOOT_ROM_UPGRADE 0x9B
+
+#define IOCTL_RELEASE_CAN_BUFFER (SIOCDEVPRIVATE + 0)
+#define IOCTL_ENABLE_BUFFERING (SIOCDEVPRIVATE + 1)
+#define IOCTL_ADD_FRAME_FILTER (SIOCDEVPRIVATE + 2)
+#define IOCTL_REMOVE_FRAME_FILTER (SIOCDEVPRIVATE + 3)
+#define IOCTL_DISABLE_BUFFERING (SIOCDEVPRIVATE + 5)
+#define IOCTL_DISABLE_ALL_BUFFERING (SIOCDEVPRIVATE + 6)
+#define IOCTL_GET_FW_BR_VERSION (SIOCDEVPRIVATE + 7)
+#define IOCTL_BEGIN_FIRMWARE_UPGRADE (SIOCDEVPRIVATE + 8)
+#define IOCTL_FIRMWARE_UPGRADE_DATA (SIOCDEVPRIVATE + 9)
+#define IOCTL_END_FIRMWARE_UPGRADE (SIOCDEVPRIVATE + 10)
+#define IOCTL_BEGIN_BOOT_ROM_UPGRADE (SIOCDEVPRIVATE + 11)
+#define IOCTL_BOOT_ROM_UPGRADE_DATA (SIOCDEVPRIVATE + 12)
+#define IOCTL_END_BOOT_ROM_UPGRADE (SIOCDEVPRIVATE + 13)
struct can_fw_resp {
u8 maj;
@@ -126,15 +164,82 @@ struct can_receive_frame {
u8 data[];
} __packed;
+struct can_config_bit_timing {
+ u8 can_if;
+ u32 brp;
+ u32 tseg1;
+ u32 tseg2;
+ u32 sjw;
+} __packed;
+
+struct vehicle_property {
+ int id;
+ u64 ts;
+ int zone;
+ int val_type;
+ u32 data_len;
+ union {
+ u8 bval;
+ int val;
+ int val_arr[4];
+ float f_value;
+ float float_arr[4];
+ u8 str[36];
+ };
+} __packed;
+
+/* IOCTL messages */
+struct rh850_release_can_buffer {
+ u8 enable;
+} __packed;
+
+struct rh850_add_can_buffer {
+ u8 can_if;
+ u32 mid;
+ u32 mask;
+} __packed;
+
+struct rh850_delete_can_buffer {
+ u8 can_if;
+ u32 mid;
+ u32 mask;
+} __packed;
+
+struct can_fw_br_resp {
+ u8 maj;
+ u8 min;
+ u8 ver[32];
+ u8 br_maj;
+ u8 br_min;
+ u8 curr_exec_mode;
+} __packed;
+
+struct rh850_ioctl_req {
+ u8 len;
+ u8 data[];
+} __packed;
+
static struct can_bittiming_const rh850_bittiming_const = {
.name = "rh850",
- .tseg1_min = 4,
+ .tseg1_min = 1,
.tseg1_max = 16,
- .tseg2_min = 2,
- .tseg2_max = 8,
+ .tseg2_min = 1,
+ .tseg2_max = 16,
.sjw_max = 4,
- .brp_min = 4,
- .brp_max = 1023,
+ .brp_min = 1,
+ .brp_max = 70,
+ .brp_inc = 1,
+};
+
+static struct can_bittiming_const rh850_data_bittiming_const = {
+ .name = "rh850",
+ .tseg1_min = 1,
+ .tseg1_max = 16,
+ .tseg2_min = 1,
+ .tseg2_max = 16,
+ .sjw_max = 4,
+ .brp_min = 1,
+ .brp_max = 70,
.brp_inc = 1,
};
@@ -165,7 +270,7 @@ static void rh850_receive_frame(struct rh850_can *priv_data,
}
netdev = priv_data->netdev[frame->can_if];
skb = alloc_can_skb(netdev, &cf);
- if (skb == NULL) {
+ if (!skb) {
LOGDE("skb alloc failed. frame->can_if %d\n", frame->can_if);
return;
}
@@ -191,9 +296,51 @@ static void rh850_receive_frame(struct rh850_can *priv_data,
netdev->stats.rx_packets++;
}
-static void rh850_process_response(struct rh850_can *priv_data,
- struct spi_miso *resp, int length)
+static void rh850_receive_property(struct rh850_can *priv_data,
+ struct vehicle_property *property)
{
+ struct canfd_frame *cfd;
+ u8 *p;
+ struct sk_buff *skb;
+ struct skb_shared_hwtstamps *skt;
+ struct timeval tv;
+ static u64 nanosec;
+ struct net_device *netdev;
+ int i;
+
+ /* can0 as the channel with properties */
+ netdev = priv_data->netdev[0];
+ skb = alloc_canfd_skb(netdev, &cfd);
+ if (!skb) {
+ LOGDE("skb alloc failed. frame->can_if %d\n", 0);
+ return;
+ }
+
+ LOGDI("rcv property:0x%x data:%2x %2x %2x %2x",
+ property->id, property->str[0], property->str[1],
+ property->str[2], property->str[3]);
+ cfd->can_id = 0x00;
+ cfd->len = sizeof(struct vehicle_property);
+
+ p = (u8 *)property;
+ for (i = 0; i < cfd->len; i++)
+ cfd->data[i] = p[i];
+
+ nanosec = le64_to_cpu(property->ts);
+ tv.tv_sec = (int)(nanosec / 1000000000);
+ tv.tv_usec = (int)(nanosec - (u64)tv.tv_sec * 1000000000) / 1000;
+ skt = skb_hwtstamps(skb);
+ skt->hwtstamp = timeval_to_ktime(tv);
+ LOGDI(" hwtstamp %lld\n", ktime_to_ms(skt->hwtstamp));
+ skb->tstamp = timeval_to_ktime(tv);
+ netif_rx(skb);
+ netdev->stats.rx_packets++;
+}
+
+static int rh850_process_response(struct rh850_can *priv_data,
+ struct spi_miso *resp, int length)
+{
+ int ret = 0;
LOGDI("<%x %2d [%d]\n", resp->cmd, resp->len, resp->seq);
if (resp->cmd == CMD_CAN_RECEIVE_FRAME) {
struct can_receive_frame *frame =
@@ -208,19 +355,55 @@ static void rh850_process_response(struct rh850_can *priv_data,
} else {
rh850_receive_frame(priv_data, frame);
}
+ } else if (resp->cmd == CMD_PROPERTY_READ) {
+ struct vehicle_property *property =
+ (struct vehicle_property *)&resp->data;
+ if (resp->len > length) {
+ LOGDE("Error. This should never happen\n");
+ LOGDE("process_response: Saving %d bytes\n",
+ length);
+ memcpy(priv_data->assembly_buffer, (char *)resp,
+ length);
+ priv_data->assembly_buffer_size = length;
+ } else {
+ rh850_receive_property(priv_data, property);
+ }
} else if (resp->cmd == CMD_GET_FW_VERSION) {
struct can_fw_resp *fw_resp = (struct can_fw_resp *)resp->data;
dev_info(&priv_data->spidev->dev, "fw %d.%d",
fw_resp->maj, fw_resp->min);
dev_info(&priv_data->spidev->dev, "fw string %s",
fw_resp->ver);
+ } else if (resp->cmd == CMD_GET_FW_BR_VERSION) {
+ struct can_fw_br_resp *fw_resp =
+ (struct can_fw_br_resp *)resp->data;
+
+ dev_info(&priv_data->spidev->dev, "fw_can %d.%d",
+ fw_resp->maj, fw_resp->min);
+ dev_info(&priv_data->spidev->dev, "fw string %s",
+ fw_resp->ver);
+ dev_info(&priv_data->spidev->dev, "fw_br %d.%d exec_mode %d",
+ fw_resp->br_maj, fw_resp->br_min,
+ fw_resp->curr_exec_mode);
+ ret = fw_resp->curr_exec_mode << 28;
+ ret |= (fw_resp->br_maj & 0xF) << 24;
+ ret |= (fw_resp->br_min & 0xFF) << 16;
+ ret |= (fw_resp->maj & 0xF) << 8;
+ ret |= (fw_resp->min & 0xFF);
+ }
+
+ if (resp->cmd == priv_data->wait_cmd) {
+ priv_data->cmd_result = ret;
+ complete(&priv_data->response_completion);
}
+ return ret;
}
-static void rh850_process_rx(struct rh850_can *priv_data, char *rx_buf)
+static int rh850_process_rx(struct rh850_can *priv_data, char *rx_buf)
{
struct spi_miso *resp;
int length_processed = 0, actual_length = priv_data->xfer_length;
+ int ret = 0;
while (length_processed < actual_length) {
int length_left = actual_length - length_processed;
@@ -237,7 +420,8 @@ static void rh850_process_rx(struct rh850_can *priv_data, char *rx_buf)
rx_buf, 2);
data = priv_data->assembly_buffer;
resp = (struct spi_miso *)data;
- length = resp->len - priv_data->assembly_buffer_size;
+ length = resp->len + sizeof(*resp)
+ - priv_data->assembly_buffer_size;
if (length > 0)
memcpy(priv_data->assembly_buffer +
priv_data->assembly_buffer_size,
@@ -258,15 +442,11 @@ static void rh850_process_rx(struct rh850_can *priv_data, char *rx_buf)
length_processed, length_left, priv_data->xfer_length);
length_processed += length;
if (length_left >= sizeof(*resp) &&
- resp->len <= length_left) {
+ resp->len + sizeof(*resp) <= length_left) {
struct spi_miso *resp =
(struct spi_miso *)data;
- if (resp->len < sizeof(struct spi_miso)) {
- LOGDE("Error resp->len is %d). Abort.\n",
- resp->len);
- break;
- }
- rh850_process_response(priv_data, resp, length_left);
+ ret = rh850_process_response(priv_data, resp,
+ length_left);
} else if (length_left > 0) {
/* Not full message. Store however much we have for */
/* later assembly */
@@ -277,6 +457,7 @@ static void rh850_process_rx(struct rh850_can *priv_data, char *rx_buf)
break;
}
}
+ return ret;
}
static int rh850_do_spi_transaction(struct rh850_can *priv_data)
@@ -291,15 +472,20 @@ static int rh850_do_spi_transaction(struct rh850_can *priv_data)
msg = kzalloc(sizeof(*msg), GFP_KERNEL);
if (xfer == 0 || msg == 0)
return -ENOMEM;
+ LOGDI(">%x %2d [%d]\n", priv_data->tx_buf[0],
+ priv_data->tx_buf[1], priv_data->tx_buf[2]);
spi_message_init(msg);
spi_message_add_tail(xfer, msg);
xfer->tx_buf = priv_data->tx_buf;
xfer->rx_buf = priv_data->rx_buf;
xfer->len = priv_data->xfer_length;
ret = spi_sync(spi, msg);
- LOGDI("spi_sync ret %d\n", ret);
+ LOGDI("spi_sync ret %d data %x %x %x %x %x %x %x %x\n", ret,
+ priv_data->rx_buf[0], priv_data->rx_buf[1], priv_data->rx_buf[2],
+ priv_data->rx_buf[3], priv_data->rx_buf[4], priv_data->rx_buf[5],
+ priv_data->rx_buf[6], priv_data->rx_buf[7]);
if (ret == 0)
- rh850_process_rx(priv_data, priv_data->rx_buf);
+ ret = rh850_process_rx(priv_data, priv_data->rx_buf);
kfree(msg);
kfree(xfer);
return ret;
@@ -347,8 +533,54 @@ static int rh850_query_firmware_version(struct rh850_can *priv_data)
return ret;
}
+static int rh850_set_bitrate(struct net_device *netdev)
+{
+ char *tx_buf, *rx_buf;
+ int ret;
+ struct spi_mosi *req;
+ struct can_config_bit_timing *req_d;
+ struct rh850_can *priv_data;
+ struct can_priv *priv = netdev_priv(netdev);
+ struct rh850_netdev_privdata *rh850_priv;
+
+ rh850_priv = netdev_priv(netdev);
+ priv_data = rh850_priv->rh850_can;
+
+ netdev_info(netdev, "ch%i, bitrate setting>%i",
+ rh850_priv->netdev_index, priv->bittiming.bitrate);
+ LOGNI("sjw>%i brp>%i ph_sg1>%i ph_sg2>%i smpl_pt>%i tq>%i pr_seg>%i",
+ priv->bittiming.sjw, priv->bittiming.brp,
+ priv->bittiming.phase_seg1,
+ priv->bittiming.phase_seg2,
+ priv->bittiming.sample_point,
+ priv->bittiming.tq, priv->bittiming.prop_seg);
+
+ mutex_lock(&priv_data->spi_lock);
+ tx_buf = priv_data->tx_buf;
+ rx_buf = priv_data->rx_buf;
+ memset(tx_buf, 0, XFER_BUFFER_SIZE);
+ memset(rx_buf, 0, XFER_BUFFER_SIZE);
+ priv_data->xfer_length = XFER_BUFFER_SIZE;
+
+ req = (struct spi_mosi *)tx_buf;
+ req->cmd = CMD_CAN_CONFIG_BIT_TIMING;
+ req->len = sizeof(struct can_config_bit_timing);
+ req->seq = atomic_inc_return(&priv_data->msg_seq);
+ req_d = (struct can_config_bit_timing *)req->data;
+ req_d->can_if = rh850_priv->netdev_index;
+ req_d->brp = priv->bittiming.brp;
+ req_d->tseg1 = priv->bittiming.phase_seg1 + priv->bittiming.prop_seg;
+ req_d->tseg2 = priv->bittiming.phase_seg2;
+ req_d->sjw = priv->bittiming.sjw;
+
+ ret = rh850_do_spi_transaction(priv_data);
+ mutex_unlock(&priv_data->spi_lock);
+
+ return ret;
+}
+
static int rh850_can_write(struct rh850_can *priv_data,
- int can_channel, struct can_frame *cf)
+ int can_channel, struct canfd_frame *cf)
{
char *tx_buf, *rx_buf;
int ret, i;
@@ -369,16 +601,29 @@ static int rh850_can_write(struct rh850_can *priv_data,
priv_data->xfer_length = XFER_BUFFER_SIZE;
req = (struct spi_mosi *)tx_buf;
- req->cmd = CMD_CAN_SEND_FRAME;
- req->len = sizeof(struct can_write_req) + 8;
- req->seq = atomic_inc_return(&priv_data->msg_seq);
-
- req_d = (struct can_write_req *)req->data;
- req_d->can_if = can_channel;
- req_d->mid = cf->can_id;
- req_d->dlc = cf->can_dlc;
- for (i = 0; i < cf->can_dlc; i++)
- req_d->data[i] = cf->data[i];
+ if (priv_data->driver_mode == DRIVER_MODE_RAW_FRAMES) {
+ req->cmd = CMD_CAN_SEND_FRAME;
+ req->len = sizeof(struct can_write_req) + 8;
+ req->seq = atomic_inc_return(&priv_data->msg_seq);
+
+ req_d = (struct can_write_req *)req->data;
+ req_d->can_if = can_channel;
+ req_d->mid = cf->can_id;
+ req_d->dlc = cf->len;
+
+ for (i = 0; i < cf->len; i++)
+ req_d->data[i] = cf->data[i];
+ } else if (priv_data->driver_mode == DRIVER_MODE_PROPERTIES ||
+ priv_data->driver_mode == DRIVER_MODE_AMB) {
+ req->cmd = CMD_PROPERTY_WRITE;
+ req->len = sizeof(struct vehicle_property);
+ req->seq = atomic_inc_return(&priv_data->msg_seq);
+ for (i = 0; i < cf->len; i++)
+ req->data[i] = cf->data[i];
+ } else {
+ LOGDE("rh850_can_write: wrong driver mode %i",
+ priv_data->driver_mode);
+ }
ret = rh850_do_spi_transaction(priv_data);
netdev = priv_data->netdev[can_channel];
@@ -414,7 +659,7 @@ static int rh850_netdev_close(struct net_device *netdev)
static void rh850_send_can_frame(struct work_struct *ws)
{
struct rh850_tx_work *tx_work;
- struct can_frame *cf;
+ struct canfd_frame *cf;
struct rh850_can *priv_data;
struct net_device *netdev;
struct rh850_netdev_privdata *netdev_priv_data;
@@ -428,7 +673,7 @@ static void rh850_send_can_frame(struct work_struct *ws)
LOGDI("send_can_frame ws %p\n", ws);
LOGDI("send_can_frame tx %p\n", tx_work);
- cf = (struct can_frame *)tx_work->skb->data;
+ cf = (struct canfd_frame *)tx_work->skb->data;
rh850_can_write(priv_data, can_channel, cf);
dev_kfree_skb(tx_work->skb);
@@ -458,10 +703,304 @@ static netdev_tx_t rh850_netdev_start_xmit(
return NETDEV_TX_OK;
}
+static int rh850_send_release_can_buffer_cmd(struct net_device *netdev)
+{
+ char *tx_buf, *rx_buf;
+ int ret;
+ struct spi_mosi *req;
+ struct rh850_can *priv_data;
+ struct rh850_netdev_privdata *netdev_priv_data;
+ int *mode;
+
+ netdev_priv_data = netdev_priv(netdev);
+ priv_data = netdev_priv_data->rh850_can;
+ mutex_lock(&priv_data->spi_lock);
+ tx_buf = priv_data->tx_buf;
+ rx_buf = priv_data->rx_buf;
+ memset(tx_buf, 0, XFER_BUFFER_SIZE);
+ memset(rx_buf, 0, XFER_BUFFER_SIZE);
+ priv_data->xfer_length = XFER_BUFFER_SIZE;
+
+ req = (struct spi_mosi *)tx_buf;
+ req->cmd = CMD_CAN_RELEASE_BUFFER;
+ req->len = sizeof(int);
+ req->seq = atomic_inc_return(&priv_data->msg_seq);
+ mode = (int *)req->data;
+ *mode = priv_data->driver_mode;
+
+ ret = rh850_do_spi_transaction(priv_data);
+ mutex_unlock(&priv_data->spi_lock);
+
+ return ret;
+}
+
+static int rh850_data_buffering(struct net_device *netdev,
+ struct ifreq *ifr, int cmd)
+{
+ char *tx_buf, *rx_buf;
+ int ret;
+ struct spi_mosi *req;
+ struct rh850_add_can_buffer *enable_buffering;
+ struct rh850_add_can_buffer *add_request;
+ struct rh850_can *priv_data;
+ struct rh850_netdev_privdata *netdev_priv_data;
+
+ netdev_priv_data = netdev_priv(netdev);
+ priv_data = netdev_priv_data->rh850_can;
+
+ mutex_lock(&priv_data->spi_lock);
+ tx_buf = priv_data->tx_buf;
+ rx_buf = priv_data->rx_buf;
+ memset(tx_buf, 0, XFER_BUFFER_SIZE);
+ memset(rx_buf, 0, XFER_BUFFER_SIZE);
+ priv_data->xfer_length = XFER_BUFFER_SIZE;
+
+ add_request = ifr->ifr_data;
+ req = (struct spi_mosi *)tx_buf;
+
+ if (cmd == IOCTL_ENABLE_BUFFERING)
+ req->cmd = CMD_CAN_DATA_BUFF_ADD;
+ else
+ req->cmd = CMD_CAN_DATA_BUFF_REMOVE;
+
+ req->len = sizeof(struct rh850_add_can_buffer);
+ req->seq = atomic_inc_return(&priv_data->msg_seq);
+
+ enable_buffering = (struct rh850_add_can_buffer *)req->data;
+ enable_buffering->can_if = add_request->can_if;
+ enable_buffering->mid = add_request->mid;
+ enable_buffering->mask = add_request->mask;
+
+ ret = rh850_do_spi_transaction(priv_data);
+ mutex_unlock(&priv_data->spi_lock);
+
+ return ret;
+}
+
+static int rh850_remove_all_buffering(struct net_device *netdev)
+{
+ char *tx_buf, *rx_buf;
+ int ret;
+ struct spi_mosi *req;
+ struct rh850_can *priv_data;
+ struct rh850_netdev_privdata *netdev_priv_data;
+
+ netdev_priv_data = netdev_priv(netdev);
+ priv_data = netdev_priv_data->rh850_can;
+
+ mutex_lock(&priv_data->spi_lock);
+ tx_buf = priv_data->tx_buf;
+ rx_buf = priv_data->rx_buf;
+ memset(tx_buf, 0, XFER_BUFFER_SIZE);
+ memset(rx_buf, 0, XFER_BUFFER_SIZE);
+ priv_data->xfer_length = XFER_BUFFER_SIZE;
+
+ req = (struct spi_mosi *)tx_buf;
+ req->cmd = CMD_CAN_DATA_BUFF_REMOVE_ALL;
+ req->len = 0;
+ req->seq = atomic_inc_return(&priv_data->msg_seq);
+
+ ret = rh850_do_spi_transaction(priv_data);
+ mutex_unlock(&priv_data->spi_lock);
+
+ return ret;
+}
+
+static int rh850_frame_filter(struct net_device *netdev,
+ struct ifreq *ifr, int cmd)
+{
+ char *tx_buf, *rx_buf;
+ int ret;
+ struct spi_mosi *req;
+ struct can_add_filter_req *add_filter;
+ struct can_add_filter_req *filter_request;
+ struct rh850_can *priv_data;
+ struct rh850_netdev_privdata *netdev_priv_data;
+
+ netdev_priv_data = netdev_priv(netdev);
+ priv_data = netdev_priv_data->rh850_can;
+
+ mutex_lock(&priv_data->spi_lock);
+ tx_buf = priv_data->tx_buf;
+ rx_buf = priv_data->rx_buf;
+ memset(tx_buf, 0, XFER_BUFFER_SIZE);
+ memset(rx_buf, 0, XFER_BUFFER_SIZE);
+ priv_data->xfer_length = XFER_BUFFER_SIZE;
+
+ filter_request = ifr->ifr_data;
+ req = (struct spi_mosi *)tx_buf;
+
+ if (cmd == IOCTL_ADD_FRAME_FILTER)
+ req->cmd = CMD_CAN_ADD_FILTER;
+ else
+ req->cmd = CMD_CAN_REMOVE_FILTER;
+
+ req->len = sizeof(struct can_add_filter_req);
+ req->seq = atomic_inc_return(&priv_data->msg_seq);
+
+ add_filter = (struct can_add_filter_req *)req->data;
+ add_filter->can_if = filter_request->can_if;
+ add_filter->mid = filter_request->mid;
+ add_filter->mask = filter_request->mask;
+
+ ret = rh850_do_spi_transaction(priv_data);
+ mutex_unlock(&priv_data->spi_lock);
+
+ return ret;
+}
+
+static int rh850_send_spi_locked(struct rh850_can *priv_data, int cmd, int len,
+ u8 *data)
+{
+ char *tx_buf, *rx_buf;
+ struct spi_mosi *req;
+ int ret;
+
+ LOGDI("rh850_send_spi_locked\n");
+
+ tx_buf = priv_data->tx_buf;
+ rx_buf = priv_data->rx_buf;
+ memset(tx_buf, 0, XFER_BUFFER_SIZE);
+ memset(rx_buf, 0, XFER_BUFFER_SIZE);
+ priv_data->xfer_length = XFER_BUFFER_SIZE;
+
+ req = (struct spi_mosi *)tx_buf;
+ req->cmd = cmd;
+ req->len = len;
+ req->seq = atomic_inc_return(&priv_data->msg_seq);
+
+ if (unlikely(len > 64))
+ return -EINVAL;
+ memcpy(req->data, data, len);
+
+ ret = rh850_do_spi_transaction(priv_data);
+ return ret;
+}
+
+static int rh850_convert_ioctl_cmd_to_spi_cmd(int ioctl_cmd)
+{
+ switch (ioctl_cmd) {
+ case IOCTL_GET_FW_BR_VERSION:
+ return CMD_GET_FW_BR_VERSION;
+ case IOCTL_BEGIN_FIRMWARE_UPGRADE:
+ return CMD_BEGIN_FIRMWARE_UPGRADE;
+ case IOCTL_FIRMWARE_UPGRADE_DATA:
+ return CMD_FIRMWARE_UPGRADE_DATA;
+ case IOCTL_END_FIRMWARE_UPGRADE:
+ return CMD_END_FIRMWARE_UPGRADE;
+ case IOCTL_BEGIN_BOOT_ROM_UPGRADE:
+ return CMD_BEGIN_BOOT_ROM_UPGRADE;
+ case IOCTL_BOOT_ROM_UPGRADE_DATA:
+ return CMD_BOOT_ROM_UPGRADE_DATA;
+ case IOCTL_END_BOOT_ROM_UPGRADE:
+ return CMD_END_BOOT_ROM_UPGRADE;
+ }
+ return -EINVAL;
+}
+
+static int rh850_do_blocking_ioctl(struct net_device *netdev,
+ struct ifreq *ifr, int cmd)
+{
+ int spi_cmd, ret;
+
+ struct rh850_can *priv_data;
+ struct rh850_netdev_privdata *netdev_priv_data;
+ struct rh850_ioctl_req *ioctl_data;
+ int len = 0;
+ u8 *data = NULL;
+
+ netdev_priv_data = netdev_priv(netdev);
+ priv_data = netdev_priv_data->rh850_can;
+
+ spi_cmd = rh850_convert_ioctl_cmd_to_spi_cmd(cmd);
+ LOGDI("rh850_do_blocking_ioctl spi_cmd %x\n", spi_cmd);
+ if (spi_cmd < 0) {
+ LOGDE("rh850_do_blocking_ioctl wrong command %d\n", cmd);
+ return spi_cmd;
+ }
+ if (!ifr)
+ return -EINVAL;
+ ioctl_data = ifr->ifr_data;
+ /* Regular NULL check fails here as ioctl_data is at some offset */
+ if ((void *)ioctl_data > (void *)0x100) {
+ len = ioctl_data->len;
+ data = ioctl_data->data;
+ }
+ LOGDI("rh850_do_blocking_ioctl len %d\n", len);
+ mutex_lock(&priv_data->spi_lock);
+
+ priv_data->wait_cmd = spi_cmd;
+ priv_data->cmd_result = -1;
+ reinit_completion(&priv_data->response_completion);
+
+ ret = rh850_send_spi_locked(priv_data, spi_cmd, len, data);
+ mutex_unlock(&priv_data->spi_lock);
+
+ if (ret == 0) {
+ LOGDI("rh850_do_blocking_ioctl ready to wait for response\n");
+ wait_for_completion_interruptible_timeout(
+ &priv_data->response_completion, 5 * HZ);
+ ret = priv_data->cmd_result;
+ }
+ return ret;
+}
+
+static int rh850_netdev_do_ioctl(struct net_device *netdev,
+ struct ifreq *ifr, int cmd)
+{
+ struct rh850_can *priv_data;
+ struct rh850_netdev_privdata *netdev_priv_data;
+ int *mode;
+ int ret = -EINVAL;
+
+ netdev_priv_data = netdev_priv(netdev);
+ priv_data = netdev_priv_data->rh850_can;
+ LOGDI("rh850_netdev_do_ioctl %x\n", cmd);
+
+ switch (cmd) {
+ case IOCTL_RELEASE_CAN_BUFFER:
+ if (ifr->ifr_data > (void *)0x100) {
+ mode = ifr->ifr_data;
+ priv_data->driver_mode = *mode;
+ }
+ LOGDE("rh850_driver_mode %d\n", priv_data->driver_mode);
+ rh850_send_release_can_buffer_cmd(netdev);
+ ret = 0;
+ break;
+ case IOCTL_ENABLE_BUFFERING:
+ case IOCTL_DISABLE_BUFFERING:
+ rh850_data_buffering(netdev, ifr, cmd);
+ ret = 0;
+ break;
+ case IOCTL_DISABLE_ALL_BUFFERING:
+ rh850_remove_all_buffering(netdev);
+ ret = 0;
+ break;
+ case IOCTL_ADD_FRAME_FILTER:
+ case IOCTL_REMOVE_FRAME_FILTER:
+ rh850_frame_filter(netdev, ifr, cmd);
+ ret = 0;
+ break;
+ case IOCTL_GET_FW_BR_VERSION:
+ case IOCTL_BEGIN_FIRMWARE_UPGRADE:
+ case IOCTL_FIRMWARE_UPGRADE_DATA:
+ case IOCTL_END_FIRMWARE_UPGRADE:
+ case IOCTL_BEGIN_BOOT_ROM_UPGRADE:
+ case IOCTL_BOOT_ROM_UPGRADE_DATA:
+ case IOCTL_END_BOOT_ROM_UPGRADE:
+ ret = rh850_do_blocking_ioctl(netdev, ifr, cmd);
+ break;
+ }
+ LOGDI("rh850_netdev_do_ioctl ret %d\n", ret);
+
+ return ret;
+}
+
static const struct net_device_ops rh850_netdev_ops = {
.ndo_open = rh850_netdev_open,
.ndo_stop = rh850_netdev_close,
.ndo_start_xmit = rh850_netdev_start_xmit,
+ .ndo_do_ioctl = rh850_netdev_do_ioctl,
};
static int rh850_create_netdev(struct spi_device *spi,
@@ -490,9 +1029,13 @@ static int rh850_create_netdev(struct spi_device *spi,
netdev->netdev_ops = &rh850_netdev_ops;
SET_NETDEV_DEV(netdev, &spi->dev);
netdev_priv_data->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES |
- CAN_CTRLMODE_LISTENONLY;
+ CAN_CTRLMODE_LISTENONLY |
+ CAN_CTRLMODE_FD;
netdev_priv_data->can.bittiming_const = &rh850_bittiming_const;
+ netdev_priv_data->can.data_bittiming_const =
+ &rh850_data_bittiming_const;
netdev_priv_data->can.clock.freq = RH850_CLOCK;
+ netdev_priv_data->can.do_set_bittiming = rh850_set_bitrate;
return 0;
}
@@ -534,9 +1077,11 @@ static struct rh850_can *rh850_create_priv_data(struct spi_device *spi)
goto cleanup_privdata;
}
priv_data->xfer_length = 0;
+ priv_data->driver_mode = DRIVER_MODE_RAW_FRAMES;
mutex_init(&priv_data->spi_lock);
atomic_set(&priv_data->msg_seq, 0);
+ init_completion(&priv_data->response_completion);
return priv_data;
cleanup_privdata:
diff --git a/drivers/platform/msm/gpio-usbdetect.c b/drivers/platform/msm/gpio-usbdetect.c
index 80e16573e0aa..dc05d7108135 100644
--- a/drivers/platform/msm/gpio-usbdetect.c
+++ b/drivers/platform/msm/gpio-usbdetect.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2013-2015,2017, 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
@@ -20,57 +20,101 @@
#include <linux/interrupt.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
-#include <linux/power_supply.h>
+#include <linux/extcon.h>
#include <linux/regulator/consumer.h>
struct gpio_usbdetect {
struct platform_device *pdev;
struct regulator *vin;
- struct power_supply *usb_psy;
int vbus_det_irq;
+ int id_det_irq;
int gpio;
+ struct extcon_dev *extcon_dev;
+ int vbus_state;
+ bool id_state;
+};
+
+static const unsigned int gpio_usb_extcon_table[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_USB_CC,
+ EXTCON_USB_SPEED,
+ EXTCON_NONE,
};
static irqreturn_t gpio_usbdetect_vbus_irq(int irq, void *data)
{
struct gpio_usbdetect *usb = data;
- int vbus;
- union power_supply_propval pval = {0,};
- vbus = gpio_get_value(usb->gpio);
- if (vbus)
- pval.intval = POWER_SUPPLY_TYPE_USB;
- else
- pval.intval = POWER_SUPPLY_TYPE_UNKNOWN;
+ usb->vbus_state = gpio_get_value(usb->gpio);
+ if (usb->vbus_state) {
+ dev_dbg(&usb->pdev->dev, "setting vbus notification\n");
+ extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB, 1);
+ } else {
+ dev_dbg(&usb->pdev->dev, "setting vbus removed notification\n");
+ extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB, 0);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t gpio_usbdetect_id_irq(int irq, void *data)
+{
+ struct gpio_usbdetect *usb = data;
+ int ret;
+
+ ret = irq_get_irqchip_state(irq, IRQCHIP_STATE_LINE_LEVEL,
+ &usb->id_state);
+ if (ret < 0) {
+ dev_err(&usb->pdev->dev, "unable to read ID IRQ LINE\n");
+ return IRQ_HANDLED;
+ }
- power_supply_set_property(usb->usb_psy,
- POWER_SUPPLY_PROP_TYPE, &pval);
+ return IRQ_WAKE_THREAD;
+}
- pval.intval = vbus;
- power_supply_set_property(usb->usb_psy, POWER_SUPPLY_PROP_PRESENT,
- &pval);
+static irqreturn_t gpio_usbdetect_id_irq_thread(int irq, void *data)
+{
+ struct gpio_usbdetect *usb = data;
+
+ if (usb->id_state) {
+ dev_dbg(&usb->pdev->dev, "stopping usb host\n");
+ extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB_HOST, 0);
+ enable_irq(usb->vbus_det_irq);
+ } else {
+ dev_dbg(&usb->pdev->dev, "starting usb HOST\n");
+ disable_irq(usb->vbus_det_irq);
+ extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB_HOST, 1);
+ }
return IRQ_HANDLED;
}
+static const u32 gpio_usb_extcon_exclusive[] = {0x3, 0};
+
static int gpio_usbdetect_probe(struct platform_device *pdev)
{
struct gpio_usbdetect *usb;
- struct power_supply *usb_psy;
int rc;
- unsigned long flags;
-
- usb_psy = power_supply_get_by_name("usb");
- if (!usb_psy) {
- dev_dbg(&pdev->dev, "USB power_supply not found, deferring probe\n");
- return -EPROBE_DEFER;
- }
usb = devm_kzalloc(&pdev->dev, sizeof(*usb), GFP_KERNEL);
if (!usb)
return -ENOMEM;
usb->pdev = pdev;
- usb->usb_psy = usb_psy;
+
+ usb->extcon_dev = devm_extcon_dev_allocate(&pdev->dev,
+ gpio_usb_extcon_table);
+ if (IS_ERR(usb->extcon_dev)) {
+ dev_err(&pdev->dev, "failed to allocate a extcon device\n");
+ return PTR_ERR(usb->extcon_dev);
+ }
+
+ usb->extcon_dev->mutually_exclusive = gpio_usb_extcon_exclusive;
+ rc = devm_extcon_dev_register(&pdev->dev, usb->extcon_dev);
+ if (rc) {
+ dev_err(&pdev->dev, "failed to register extcon device\n");
+ return rc;
+ }
if (of_get_property(pdev->dev.of_node, "vin-supply", NULL)) {
usb->vin = devm_regulator_get(&pdev->dev, "vin");
@@ -94,43 +138,63 @@ static int gpio_usbdetect_probe(struct platform_device *pdev)
"qcom,vbus-det-gpio", 0);
if (usb->gpio < 0) {
dev_err(&pdev->dev, "Failed to get gpio: %d\n", usb->gpio);
- return usb->gpio;
+ rc = usb->gpio;
+ goto error;
}
rc = gpio_request(usb->gpio, "vbus-det-gpio");
if (rc < 0) {
dev_err(&pdev->dev, "Failed to request gpio: %d\n", rc);
- return rc;
+ goto error;
}
usb->vbus_det_irq = gpio_to_irq(usb->gpio);
if (usb->vbus_det_irq < 0) {
- if (usb->vin)
- regulator_disable(usb->vin);
- return usb->vbus_det_irq;
+ dev_err(&pdev->dev, "get vbus_det_irq failed\n");
+ rc = usb->vbus_det_irq;
+ goto error;
}
- rc = devm_request_irq(&pdev->dev, usb->vbus_det_irq,
- gpio_usbdetect_vbus_irq,
- IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
- "vbus_det_irq", usb);
+ rc = devm_request_threaded_irq(&pdev->dev, usb->vbus_det_irq,
+ NULL, gpio_usbdetect_vbus_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT, "vbus_det_irq", usb);
if (rc) {
dev_err(&pdev->dev, "request for vbus_det_irq failed: %d\n",
rc);
- if (usb->vin)
- regulator_disable(usb->vin);
- return rc;
+ goto error;
+ }
+
+ usb->id_det_irq = platform_get_irq_byname(pdev, "pmic_id_irq");
+ if (usb->id_det_irq < 0) {
+ dev_err(&pdev->dev, "get id_det_irq failed\n");
+ rc = usb->id_det_irq;
+ goto error;
+ }
+
+ rc = devm_request_threaded_irq(&pdev->dev, usb->id_det_irq,
+ gpio_usbdetect_id_irq,
+ gpio_usbdetect_id_irq_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT, "id_det_irq", usb);
+ if (rc) {
+ dev_err(&pdev->dev, "request for id_det_irq failed: %d\n", rc);
+ goto error;
}
enable_irq_wake(usb->vbus_det_irq);
+ enable_irq_wake(usb->id_det_irq);
dev_set_drvdata(&pdev->dev, usb);
/* Read and report initial VBUS state */
- local_irq_save(flags);
gpio_usbdetect_vbus_irq(usb->vbus_det_irq, usb);
- local_irq_restore(flags);
return 0;
+
+error:
+ if (usb->vin)
+ regulator_disable(usb->vin);
+ return rc;
}
static int gpio_usbdetect_remove(struct platform_device *pdev)
@@ -139,6 +203,8 @@ static int gpio_usbdetect_remove(struct platform_device *pdev)
disable_irq_wake(usb->vbus_det_irq);
disable_irq(usb->vbus_det_irq);
+ disable_irq_wake(usb->id_det_irq);
+ disable_irq(usb->id_det_irq);
if (usb->vin)
regulator_disable(usb->vin);
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 34b0adb108eb..ea008ffbc856 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -367,6 +367,14 @@ config MSM_SPM
driver allows configuring SPM to allow different low power modes for
both core and L2.
+config MSM_L2_SPM
+ bool "SPM support for L2 cache"
+ help
+ Enable SPM driver support for L2 cache. Some MSM chipsets allow
+ control of L2 cache low power mode with a Subsystem Power manager.
+ Enabling this driver allows configuring L2 SPM for low power modes
+ on supported chipsets
+
config QCOM_SCM
bool "Secure Channel Manager (SCM) support"
default n
@@ -573,6 +581,16 @@ config MSM_BOOT_STATS
This figures are reported in mpm sleep clock cycles and have a
resolution of 31 bits as 1 bit is used as an overflow check.
+config MSM_BOOT_TIME_MARKER
+ bool "Use MSM boot time marker reporting"
+ depends on MSM_BOOT_STATS
+ help
+ Use this to mark msm boot kpi for measurement.
+ An instrumentation for boot time measurement.
+ To create an entry, call "place_marker" function.
+ At userspace, write marker name to "/sys/kernel/debug/bootkpi/kpi_values"
+ If unsure, say N
+
config QCOM_CPUSS_DUMP
bool "CPU Subsystem Dumping support"
help
@@ -900,4 +918,28 @@ config QCOM_CX_IPEAK
clients are going to cross their thresholds then Cx ipeak hw module will raise
an interrupt to cDSP block to throttle cDSP fmax.
+config MSM_CACHE_M4M_ERP64
+ bool "Cache and M4M error report"
+ depends on ARCH_MSM8996
+ help
+ Say 'Y' here to enable reporting of cache and M4M errors to the kernel
+ log. The kernel log contains collected error syndrome and address
+ registers. These register dumps can be used as useful information
+ to find out possible hardware problems.
+
+config MSM_CACHE_M4M_ERP64_PANIC_ON_CE
+ bool "Panic on correctable cache/M4M errors"
+ help
+ Say 'Y' here to cause kernel panic when correctable cache/M4M errors
+ are detected. Enabling this is useful when you want to dump memory
+ and system state close to the time when the error occured.
+
+ If unsure, say N.
+
+config MSM_CACHE_M4M_ERP64_PANIC_ON_UE
+ bool "Panic on uncorrectable cache/M4M errors"
+ help
+ Say 'Y' here to cause kernel panic when uncorrectable cache/M4M errors
+ are detected.
+
source "drivers/soc/qcom/memshare/Kconfig"
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 87698b75d3b8..5eeede23333d 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -86,6 +86,7 @@ obj-$(CONFIG_MSM_CORE_HANG_DETECT) += core_hang_detect.o
obj-$(CONFIG_MSM_GLADIATOR_HANG_DETECT) += gladiator_hang_detect.o
obj-$(CONFIG_MSM_RUN_QUEUE_STATS) += msm_rq_stats.o
obj-$(CONFIG_MSM_BOOT_STATS) += boot_stats.o
+obj-$(CONFIG_MSM_BOOT_TIME_MARKER) += boot_marker.o
obj-$(CONFIG_MSM_AVTIMER) += avtimer.o
ifdef CONFIG_ARCH_MSM8996
obj-$(CONFIG_HW_PERF_EVENTS) += perf_event_kryo.o
@@ -104,3 +105,4 @@ obj-$(CONFIG_WCD_DSP_GLINK) += wcd-dsp-glink.o
obj-$(CONFIG_QCOM_SMCINVOKE) += smcinvoke.o
obj-$(CONFIG_QCOM_EARLY_RANDOM) += early_random.o
obj-$(CONFIG_QCOM_CX_IPEAK) += cx_ipeak.o
+obj-$(CONFIG_MSM_CACHE_M4M_ERP64) += cache_m4m_erp64.o
diff --git a/drivers/soc/qcom/boot_marker.c b/drivers/soc/qcom/boot_marker.c
new file mode 100644
index 000000000000..b3a6c9f8d054
--- /dev/null
+++ b/drivers/soc/qcom/boot_marker.c
@@ -0,0 +1,183 @@
+/* Copyright (c) 2016, 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/kernel.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/export.h>
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <soc/qcom/boot_stats.h>
+
+#define MAX_STRING_LEN 256
+#define BOOT_MARKER_MAX_LEN 40
+static struct dentry *dent_bkpi, *dent_bkpi_status;
+static struct boot_marker boot_marker_list;
+
+struct boot_marker {
+ char marker_name[BOOT_MARKER_MAX_LEN];
+ unsigned long long int timer_value;
+ struct list_head list;
+ struct mutex lock;
+};
+
+static void _create_boot_marker(const char *name,
+ unsigned long long int timer_value)
+{
+ struct boot_marker *new_boot_marker;
+
+ pr_debug("%-41s:%llu.%03llu seconds\n", name,
+ timer_value/TIMER_KHZ,
+ ((timer_value % TIMER_KHZ)
+ * 1000) / TIMER_KHZ);
+
+ new_boot_marker = kmalloc(sizeof(*new_boot_marker), GFP_KERNEL);
+ if (!new_boot_marker)
+ return;
+
+ strlcpy(new_boot_marker->marker_name, name,
+ sizeof(new_boot_marker->marker_name));
+ new_boot_marker->timer_value = timer_value;
+
+ mutex_lock(&boot_marker_list.lock);
+ list_add_tail(&(new_boot_marker->list), &(boot_marker_list.list));
+ mutex_unlock(&boot_marker_list.lock);
+}
+
+static void set_bootloader_stats(void)
+{
+ _create_boot_marker("M - APPSBL Start - ",
+ readl_relaxed(&boot_stats->bootloader_start));
+ _create_boot_marker("M - APPSBL Display Init - ",
+ readl_relaxed(&boot_stats->bootloader_display));
+ _create_boot_marker("M - APPSBL Early-Domain Start - ",
+ readl_relaxed(&boot_stats->bootloader_early_domain_start));
+ _create_boot_marker("D - APPSBL Kernel Load Time - ",
+ readl_relaxed(&boot_stats->bootloader_load_kernel));
+ _create_boot_marker("D - APPSBL Kernel Auth Time - ",
+ readl_relaxed(&boot_stats->bootloader_checksum));
+ _create_boot_marker("M - APPSBL End - ",
+ readl_relaxed(&boot_stats->bootloader_end));
+}
+
+void place_marker(const char *name)
+{
+ _create_boot_marker((char *) name, msm_timer_get_sclk_ticks());
+}
+EXPORT_SYMBOL(place_marker);
+
+static ssize_t bootkpi_reader(struct file *fp, char __user *user_buffer,
+ size_t count, loff_t *position)
+{
+ int rc = 0;
+ char *buf;
+ int temp = 0;
+ struct boot_marker *marker;
+
+ buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ mutex_lock(&boot_marker_list.lock);
+ list_for_each_entry(marker, &boot_marker_list.list, list) {
+ temp += scnprintf(buf + temp, PAGE_SIZE - temp,
+ "%-41s:%llu.%03llu seconds\n",
+ marker->marker_name,
+ marker->timer_value/TIMER_KHZ,
+ (((marker->timer_value % TIMER_KHZ)
+ * 1000) / TIMER_KHZ));
+ }
+ mutex_unlock(&boot_marker_list.lock);
+ rc = simple_read_from_buffer(user_buffer, count, position, buf, temp);
+ kfree(buf);
+ return rc;
+}
+
+static ssize_t bootkpi_writer(struct file *fp, const char __user *user_buffer,
+ size_t count, loff_t *position)
+{
+ int rc = 0;
+ char buf[MAX_STRING_LEN];
+
+ if (count > MAX_STRING_LEN)
+ return -EINVAL;
+ rc = simple_write_to_buffer(buf,
+ sizeof(buf) - 1, position, user_buffer, count);
+ if (rc < 0)
+ return rc;
+ buf[rc] = '\0';
+ place_marker(buf);
+ return rc;
+}
+
+static int bootkpi_open(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static const struct file_operations fops_bkpi = {
+ .owner = THIS_MODULE,
+ .open = bootkpi_open,
+ .read = bootkpi_reader,
+ .write = bootkpi_writer,
+};
+
+static int __init init_bootkpi(void)
+{
+ dent_bkpi = debugfs_create_dir("bootkpi", NULL);
+ if (IS_ERR_OR_NULL(dent_bkpi))
+ return -ENODEV;
+
+ dent_bkpi_status = debugfs_create_file("kpi_values",
+ (S_IRUGO|S_IWUGO), dent_bkpi, 0, &fops_bkpi);
+ if (IS_ERR_OR_NULL(dent_bkpi_status)) {
+ debugfs_remove(dent_bkpi);
+ dent_bkpi = NULL;
+ pr_err("boot_marker: Could not create 'kpi_values' debugfs file\n");
+ return -ENODEV;
+ }
+
+ INIT_LIST_HEAD(&boot_marker_list.list);
+ mutex_init(&boot_marker_list.lock);
+ set_bootloader_stats();
+ return 0;
+}
+subsys_initcall(init_bootkpi);
+
+static void __exit exit_bootkpi(void)
+{
+ struct boot_marker *marker;
+ struct boot_marker *temp_addr;
+
+ debugfs_remove_recursive(dent_bkpi);
+ mutex_lock(&boot_marker_list.lock);
+ list_for_each_entry_safe(marker, temp_addr, &boot_marker_list.list,
+ list) {
+ list_del(&marker->list);
+ kfree(marker);
+ }
+ mutex_unlock(&boot_marker_list.lock);
+ boot_stats_exit();
+}
+module_exit(exit_bootkpi);
+
+MODULE_DESCRIPTION("MSM boot key performance indicators");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/soc/qcom/boot_stats.c b/drivers/soc/qcom/boot_stats.c
index 2fc9cbf55d4b..eb5357e892eb 100644
--- a/drivers/soc/qcom/boot_stats.c
+++ b/drivers/soc/qcom/boot_stats.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2013-2014,2016, 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
@@ -15,6 +15,7 @@
#include <linux/io.h>
#include <linux/init.h>
#include <linux/delay.h>
+#include <linux/slab.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
@@ -22,17 +23,13 @@
#include <linux/sched.h>
#include <linux/of.h>
#include <linux/of_address.h>
-
-struct boot_stats {
- uint32_t bootloader_start;
- uint32_t bootloader_end;
- uint32_t bootloader_display;
- uint32_t bootloader_load_kernel;
-};
+#include <linux/export.h>
+#include <linux/types.h>
+#include <soc/qcom/boot_stats.h>
static void __iomem *mpm_counter_base;
static uint32_t mpm_counter_freq;
-static struct boot_stats __iomem *boot_stats;
+struct boot_stats __iomem *boot_stats;
static int mpm_parse_dt(void)
{
@@ -88,6 +85,42 @@ static void print_boot_stats(void)
mpm_counter_freq);
}
+unsigned long long int msm_timer_get_sclk_ticks(void)
+{
+ unsigned long long int t1, t2;
+ int loop_count = 10;
+ int loop_zero_count = 3;
+ int tmp = USEC_PER_SEC;
+ void __iomem *sclk_tick;
+
+ do_div(tmp, TIMER_KHZ);
+ tmp /= (loop_zero_count-1);
+ sclk_tick = mpm_counter_base;
+ if (!sclk_tick)
+ return -EINVAL;
+ while (loop_zero_count--) {
+ t1 = __raw_readl_no_log(sclk_tick);
+ do {
+ udelay(1);
+ t2 = t1;
+ t1 = __raw_readl_no_log(sclk_tick);
+ } while ((t2 != t1) && --loop_count);
+ if (!loop_count) {
+ pr_err("boot_stats: SCLK did not stabilize\n");
+ return 0;
+ }
+ if (t1)
+ break;
+
+ udelay(tmp);
+ }
+ if (!loop_zero_count) {
+ pr_err("boot_stats: SCLK reads zero\n");
+ return 0;
+ }
+ return t1;
+}
+
int boot_stats_init(void)
{
int ret;
@@ -98,9 +131,14 @@ int boot_stats_init(void)
print_boot_stats();
+ if (!(boot_marker_enabled()))
+ boot_stats_exit();
+ return 0;
+}
+
+int boot_stats_exit(void)
+{
iounmap(boot_stats);
iounmap(mpm_counter_base);
-
return 0;
}
-
diff --git a/drivers/soc/qcom/cache_m4m_erp64.c b/drivers/soc/qcom/cache_m4m_erp64.c
new file mode 100644
index 000000000000..758e9d03e07b
--- /dev/null
+++ b/drivers/soc/qcom/cache_m4m_erp64.c
@@ -0,0 +1,635 @@
+/*
+ * Copyright (c) 2012-2015, 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) "msm_cache_erp64: " fmt
+
+#include <linux/printk.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/cpu.h>
+#include <linux/workqueue.h>
+#include <linux/of.h>
+#include <linux/cpu_pm.h>
+#include <linux/smp.h>
+
+#include <soc/qcom/kryo-l2-accessors.h>
+
+/* Instruction cache */
+#define ICECR_EL1 S3_1_c11_c1_0
+#define ICECR_IRQ_EN (BIT(1) | BIT(3) | BIT(5) | BIT(7))
+#define ICESR_EL1 S3_1_c11_c1_1
+#define ICESR_BIT_L1DPE BIT(3)
+#define ICESR_BIT_L1TPE BIT(2)
+#define ICESR_BIT_L0DPE BIT(1)
+#define ICESR_BIT_L0TPE BIT(0)
+#define ICESYNR0_EL1 S3_1_c11_c1_3
+#define ICESYNR1_EL1 S3_1_c11_c1_4
+#define ICEAR0_EL1 S3_1_c11_c1_5
+#define ICEAR1_EL1 S3_1_c11_c1_6
+#define ICESRS_EL1 S3_1_c11_c1_2
+
+/* Data cache */
+#define DCECR_EL1 S3_1_c11_c5_0
+#define DCECR_IRQ_EN (BIT(1) | BIT(3) | BIT(5) | BIT(7) | \
+ BIT(9))
+#define DCESR_EL1 S3_1_c11_c5_1
+#define DCESR_BIT_S1FTLBDPE BIT(4)
+#define DCESR_BIT_S1FTLBTPE BIT(3)
+#define DCESR_BIT_L1DPE BIT(2)
+#define DCESR_BIT_L1PTPE BIT(1)
+#define DCESR_BIT_L1VTPE BIT(0)
+#define DCESYNR0_EL1 S3_1_c11_c5_3
+#define DCESYNR1_EL1 S3_1_c11_c5_4
+#define DCESRS_EL1 S3_1_c11_c5_2
+#define DCEAR0_EL1 S3_1_c11_c5_5
+#define DCEAR1_EL1 S3_1_c11_c5_6
+
+/* L2 cache */
+#define L2CPUSRSELR_EL1I S3_3_c15_c0_6
+#define L2CPUSRDR_EL1 S3_3_c15_c0_7
+#define L2ECR0_IA 0x200
+#define L2ECR0_IRQ_EN (BIT(1) | BIT(3) | BIT(6) | BIT(9) | \
+ BIT(11) | BIT(13) | BIT(16) | \
+ BIT(19) | BIT(21) | BIT(23) | \
+ BIT(26) | BIT(29))
+
+#define L2ECR1_IA 0x201
+#define L2ECR1_IRQ_EN (BIT(1) | BIT(3) | BIT(6) | BIT(9) | \
+ BIT(11) | BIT(13) | BIT(16) | \
+ BIT(19) | BIT(21) | BIT(23) | BIT(29))
+#define L2ECR2_IA 0x202
+#define L2ECR2_IRQ_EN_MASK 0x3FFFFFF
+#define L2ECR2_IRQ_EN (BIT(1) | BIT(3) | BIT(6) | BIT(9) | \
+ BIT(12) | BIT(15) | BIT(17) | \
+ BIT(19) | BIT(22) | BIT(25))
+#define L2ESR0_IA 0x204
+#define L2ESR0_MASK 0x00FFFFFF
+#define L2ESR0_CE ((BIT(0) | BIT(1) | BIT(2) | BIT(3) | \
+ BIT(4) | BIT(5) | BIT(12) | BIT(13) | \
+ BIT(14) | BIT(15) | BIT(16) | BIT(17)) \
+ & L2ESR0_MASK)
+#define L2ESR0_UE (~L2ESR0_CE & L2ESR0_MASK)
+#define L2ESRS0_IA 0x205
+#define L2ESR1_IA 0x206
+#define L2ESR1_MASK 0x80FFFBFF
+#define L2ESRS1_IA 0x207
+#define L2ESYNR0_IA 0x208
+#define L2ESYNR1_IA 0x209
+#define L2ESYNR2_IA 0x20A
+#define L2ESYNR3_IA 0x20B
+#define L2ESYNR4_IA 0x20C
+#define L2EAR0_IA 0x20E
+#define L2EAR1_IA 0x20F
+
+#define L3_QLL_HML3_FIRA 0x3000
+#define L3_QLL_HML3_FIRA_CE (BIT(1) | BIT(3) | BIT(5))
+#define L3_QLL_HML3_FIRA_UE (BIT(2) | BIT(4) | BIT(6))
+#define L3_QLL_HML3_FIRAC 0x3008
+#define L3_QLL_HML3_FIRAS 0x3010
+#define L3_QLL_HML3_FIRAT0C 0x3020
+#define L3_QLL_HML3_FIRAT0C_IRQ_EN 0xFFFFFFFF
+#define L3_QLL_HML3_FIRAT1C 0x3024
+#define L3_QLL_HML3_FIRAT1S 0x302C
+#define L3_QLL_HML3_FIRAT1S_IRQ_EN 0x01EFC8FE
+#define L3_QLL_HML3_FIRSYNA 0x3100
+#define L3_QLL_HML3_FIRSYNB 0x3104
+#define L3_QLL_HML3_FIRSYNC 0x3108
+#define L3_QLL_HML3_FIRSYND 0x310C
+
+#define M4M_ERR_STATUS 0x10000
+#define M4M_ERR_STATUS_MASK 0x1FF
+#define M4M_ERR_Q22SIB_RET_DEC_ERR (BIT(7))
+#define M4M_ERR_Q22SIB_RET_SLV_ERR (BIT(6))
+#define M4M_ERR_CLR 0x10008
+#define M4M_INT_CTRL 0x10010
+#define M4M_INT_CTRL_IRQ_EN 0x1FF
+#define M4M_ERR_CTRL 0x10018
+#define M4M_ERR_INJ 0x10020
+#define M4M_ERR_CAP_0 0x10030
+#define M4M_ERR_CAP_1 0x10038
+#define M4M_ERR_CAP_2 0x10040
+#define M4M_ERR_CAP_3 0x10048
+
+#define AFFINITY_LEVEL_L3 3
+
+#ifdef CONFIG_MSM_CACHE_M4M_ERP64_PANIC_ON_CE
+static bool __read_mostly panic_on_ce = true;
+#else
+static bool __read_mostly panic_on_ce;
+#endif
+
+#ifdef CONFIG_MSM_CACHE_M4M_ERP64_PANIC_ON_UE
+static bool __read_mostly panic_on_ue = true;
+#else
+static bool __read_mostly panic_on_ue;
+#endif
+
+module_param(panic_on_ce, bool, false);
+module_param(panic_on_ue, bool, false);
+
+static void __iomem *hml3_base;
+static void __iomem *m4m_base;
+
+enum erp_irq_index { IRQ_L1, IRQ_L2_INFO0, IRQ_L2_INFO1, IRQ_L2_ERR0,
+ IRQ_L2_ERR1, IRQ_L3, IRQ_M4M, IRQ_MAX };
+static const char * const erp_irq_names[] = {
+ "l1_irq", "l2_irq_info_0", "l2_irq_info_1", "l2_irq_err_0",
+ "l2_irq_err_1", "l3_irq", "m4m_irq"
+};
+static int erp_irqs[IRQ_MAX];
+
+struct msm_l1_err_stats {
+ /* nothing */
+};
+
+static DEFINE_PER_CPU(struct msm_l1_err_stats, msm_l1_erp_stats);
+static DEFINE_PER_CPU(struct call_single_data, handler_csd);
+
+#define erp_mrs(reg) ({ \
+ u64 __val; \
+ asm volatile("mrs %0, " __stringify(reg) : "=r" (__val)); \
+ __val; \
+})
+
+#define erp_msr(reg, val) { \
+ asm volatile("msr " __stringify(reg) ", %0" : : "r" (val)); \
+}
+
+static void msm_erp_show_icache_error(void)
+{
+ u64 icesr;
+ int cpu = raw_smp_processor_id();
+
+ icesr = erp_mrs(ICESR_EL1);
+ if (!(icesr & (ICESR_BIT_L0TPE | ICESR_BIT_L0DPE | ICESR_BIT_L1TPE |
+ ICESR_BIT_L1DPE))) {
+ pr_debug("CPU%d: No I-cache error detected ICESR 0x%llx\n",
+ cpu, icesr);
+ goto clear_out;
+ }
+
+ pr_alert("CPU%d: I-cache error\n", cpu);
+ pr_alert("CPU%d: ICESR_EL1 0x%llx ICESYNR0 0x%llx ICESYNR1 0x%llx ICEAR0 0x%llx IECAR1 0x%llx\n",
+ cpu, icesr, erp_mrs(ICESYNR0_EL1), erp_mrs(ICESYNR1_EL1),
+ erp_mrs(ICEAR0_EL1), erp_mrs(ICEAR1_EL1));
+
+ /*
+ * all detectable I-cache erros are recoverable as
+ * corrupted lines are refetched
+ */
+ if (panic_on_ce)
+ BUG_ON(1);
+ else
+ WARN_ON(1);
+
+clear_out:
+ erp_msr(ICESR_EL1, icesr);
+}
+
+static void msm_erp_show_dcache_error(void)
+{
+ u64 dcesr;
+ int cpu = raw_smp_processor_id();
+
+ dcesr = erp_mrs(DCESR_EL1);
+ if (!(dcesr & (DCESR_BIT_L1VTPE | DCESR_BIT_L1PTPE | DCESR_BIT_L1DPE |
+ DCESR_BIT_S1FTLBTPE | DCESR_BIT_S1FTLBDPE))) {
+ pr_debug("CPU%d: No D-cache error detected DCESR 0x%llx\n",
+ cpu, dcesr);
+ goto clear_out;
+ }
+
+ pr_alert("CPU%d: D-cache error detected\n", cpu);
+ pr_alert("CPU%d: L1 DCESR 0x%llx, DCESYNR0 0x%llx, DCESYNR1 0x%llx, DCEAR0 0x%llx, DCEAR1 0x%llx\n",
+ cpu, dcesr, erp_mrs(DCESYNR0_EL1), erp_mrs(DCESYNR1_EL1),
+ erp_mrs(DCEAR0_EL1), erp_mrs(DCEAR1_EL1));
+
+ /* all D-cache erros are correctable */
+ if (panic_on_ce)
+ BUG_ON(1);
+ else
+ WARN_ON(1);
+
+clear_out:
+ erp_msr(DCESR_EL1, dcesr);
+}
+
+static irqreturn_t msm_l1_erp_irq(int irq, void *dev_id)
+{
+ msm_erp_show_icache_error();
+ msm_erp_show_dcache_error();
+ return IRQ_HANDLED;
+}
+
+static DEFINE_SPINLOCK(local_handler_lock);
+static void msm_l2_erp_local_handler(void *force)
+{
+ unsigned long flags;
+ u64 esr0, esr1;
+ bool parity_ue, parity_ce, misc_ue;
+ int cpu;
+
+ spin_lock_irqsave(&local_handler_lock, flags);
+
+ esr0 = get_l2_indirect_reg(L2ESR0_IA);
+ esr1 = get_l2_indirect_reg(L2ESR1_IA);
+ parity_ue = esr0 & L2ESR0_UE;
+ parity_ce = esr0 & L2ESR0_CE;
+ misc_ue = esr1;
+ cpu = raw_smp_processor_id();
+
+ if (force || parity_ue || parity_ce || misc_ue) {
+ if (parity_ue)
+ pr_alert("CPU%d: L2 uncorrectable parity error\n", cpu);
+ if (parity_ce)
+ pr_alert("CPU%d: L2 correctable parity error\n", cpu);
+ if (misc_ue)
+ pr_alert("CPU%d: L2 (non-parity) error\n", cpu);
+ pr_alert("CPU%d: L2ESR0 0x%llx, L2ESR1 0x%llx\n",
+ cpu, esr0, esr1);
+ pr_alert("CPU%d: L2ESYNR0 0x%llx, L2ESYNR1 0x%llx, L2ESYNR2 0x%llx\n",
+ cpu, get_l2_indirect_reg(L2ESYNR0_IA),
+ get_l2_indirect_reg(L2ESYNR1_IA),
+ get_l2_indirect_reg(L2ESYNR2_IA));
+ pr_alert("CPU%d: L2EAR0 0x%llx, L2EAR1 0x%llx\n", cpu,
+ get_l2_indirect_reg(L2EAR0_IA),
+ get_l2_indirect_reg(L2EAR1_IA));
+ } else {
+ pr_info("CPU%d: No L2 error detected in L2ESR0 0x%llx, L2ESR1 0x%llx)\n",
+ cpu, esr0, esr1);
+ }
+
+ /* clear */
+ set_l2_indirect_reg(L2ESR0_IA, esr0);
+ set_l2_indirect_reg(L2ESR1_IA, esr1);
+
+ if (panic_on_ue)
+ BUG_ON(parity_ue || misc_ue);
+ else
+ WARN_ON(parity_ue || misc_ue);
+
+ if (panic_on_ce)
+ BUG_ON(parity_ce);
+ else
+ WARN_ON(parity_ce);
+
+ spin_unlock_irqrestore(&local_handler_lock, flags);
+}
+
+static irqreturn_t msm_l2_erp_irq(int irq, void *dev_id)
+{
+ int cpu;
+ struct call_single_data *csd;
+
+ for_each_online_cpu(cpu) {
+ csd = &per_cpu(handler_csd, cpu);
+ csd->func = msm_l2_erp_local_handler;
+ smp_call_function_single_async(cpu, csd);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t msm_l3_erp_irq(int irq, void *dev_id)
+{
+ u32 hml3_fira;
+ bool parity_ue, parity_ce, misc_ue;
+
+ hml3_fira = readl_relaxed(hml3_base + L3_QLL_HML3_FIRA);
+ parity_ue = (hml3_fira & L3_QLL_HML3_FIRAT1S_IRQ_EN) &
+ L3_QLL_HML3_FIRA_UE;
+ parity_ce = (hml3_fira & L3_QLL_HML3_FIRAT1S_IRQ_EN) &
+ L3_QLL_HML3_FIRA_CE;
+ misc_ue = (hml3_fira & L3_QLL_HML3_FIRAT1S_IRQ_EN) &
+ ~(L3_QLL_HML3_FIRA_UE | L3_QLL_HML3_FIRA_CE);
+ if (parity_ue)
+ pr_alert("L3 uncorrectable parity error\n");
+ if (parity_ce)
+ pr_alert("L3 correctable parity error\n");
+ if (misc_ue)
+ pr_alert("L3 (non-parity) error\n");
+
+ pr_alert("HML3_FIRA 0x%0x\n", hml3_fira);
+ pr_alert("HML3_FIRSYNA 0x%0x, HML3_FIRSYNB 0x%0x\n",
+ readl_relaxed(hml3_base + L3_QLL_HML3_FIRSYNA),
+ readl_relaxed(hml3_base + L3_QLL_HML3_FIRSYNB));
+ pr_alert("HML3_FIRSYNC 0x%0x, HML3_FIRSYND 0x%0x\n",
+ readl_relaxed(hml3_base + L3_QLL_HML3_FIRSYNC),
+ readl_relaxed(hml3_base + L3_QLL_HML3_FIRSYND));
+
+ if (panic_on_ue)
+ BUG_ON(parity_ue || misc_ue);
+ else
+ WARN_ON(parity_ue || misc_ue);
+
+ if (panic_on_ce)
+ BUG_ON(parity_ce);
+ else
+ WARN_ON(parity_ce);
+
+ writel_relaxed(hml3_fira, hml3_base + L3_QLL_HML3_FIRAC);
+ /* ensure of irq clear */
+ wmb();
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t msm_m4m_erp_irq(int irq, void *dev_id)
+{
+ u32 m4m_status;
+
+ pr_alert("CPU%d: M4M error detected\n", raw_smp_processor_id());
+ m4m_status = readl_relaxed(m4m_base + M4M_ERR_STATUS);
+ pr_alert("M4M_ERR_STATUS 0x%0x\n", m4m_status);
+ if ((m4m_status & M4M_ERR_STATUS_MASK) &
+ ~(M4M_ERR_Q22SIB_RET_DEC_ERR | M4M_ERR_Q22SIB_RET_SLV_ERR)) {
+ pr_alert("M4M_ERR_CAP_0 0x%0x, M4M_ERR_CAP_1 0x%x\n",
+ readl_relaxed(m4m_base + M4M_ERR_CAP_0),
+ readl_relaxed(m4m_base + M4M_ERR_CAP_1));
+ pr_alert("M4M_ERR_CAP_2 0x%0x, M4M_ERR_CAP_3 0x%x\n",
+ readl_relaxed(m4m_base + M4M_ERR_CAP_2),
+ readl_relaxed(m4m_base + M4M_ERR_CAP_3));
+ } else {
+ /*
+ * M4M error-capture registers not valid when error detected
+ * due to DEC_ERR or SLV_ERR. L2E registers are still valid.
+ */
+ pr_alert("Omit dumping M4M_ERR_CAP\n");
+ }
+
+ /*
+ * On QSB errors, the L2 captures the bad address and syndrome in
+ * L2E error registers. Therefore dump L2E always whenever M4M error
+ * detected.
+ */
+ on_each_cpu(msm_l2_erp_local_handler, (void *)1, 1);
+ writel_relaxed(1, m4m_base + M4M_ERR_CLR);
+ /* ensure of irq clear */
+ wmb();
+
+ if (panic_on_ue)
+ BUG_ON(1);
+ else
+ WARN_ON(1);
+
+ return IRQ_HANDLED;
+}
+
+static void enable_erp_irq_callback(void *info)
+{
+ enable_percpu_irq(erp_irqs[IRQ_L1], IRQ_TYPE_NONE);
+}
+
+static void disable_erp_irq_callback(void *info)
+{
+ disable_percpu_irq(erp_irqs[IRQ_L1]);
+}
+
+static void msm_cache_erp_irq_init(void *param)
+{
+ u64 v;
+ /* Enable L0/L1 I/D cache error reporting. */
+ erp_msr(ICECR_EL1, ICECR_IRQ_EN);
+ erp_msr(DCECR_EL1, DCECR_IRQ_EN);
+ /*
+ * Enable L2 data, tag, QSB and possion error reporting.
+ */
+ set_l2_indirect_reg(L2ECR0_IA, L2ECR0_IRQ_EN);
+ set_l2_indirect_reg(L2ECR1_IA, L2ECR1_IRQ_EN);
+ v = (get_l2_indirect_reg(L2ECR2_IA) & ~L2ECR2_IRQ_EN_MASK)
+ | L2ECR2_IRQ_EN;
+ set_l2_indirect_reg(L2ECR2_IA, v);
+}
+
+static void msm_cache_erp_l3_init(void)
+{
+ writel_relaxed(L3_QLL_HML3_FIRAT0C_IRQ_EN,
+ hml3_base + L3_QLL_HML3_FIRAT0C);
+ writel_relaxed(L3_QLL_HML3_FIRAT1S_IRQ_EN,
+ hml3_base + L3_QLL_HML3_FIRAT1S);
+}
+
+static int cache_erp_cpu_pm_callback(struct notifier_block *self,
+ unsigned long cmd, void *v)
+{
+ unsigned long aff_level = (unsigned long) v;
+
+ switch (cmd) {
+ case CPU_CLUSTER_PM_EXIT:
+ msm_cache_erp_irq_init(NULL);
+
+ if (aff_level >= AFFINITY_LEVEL_L3)
+ msm_cache_erp_l3_init();
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+static struct notifier_block cache_erp_cpu_pm_notifier = {
+ .notifier_call = cache_erp_cpu_pm_callback,
+};
+
+static int cache_erp_cpu_callback(struct notifier_block *nfb,
+ unsigned long action, void *hcpu)
+{
+ switch (action & ~CPU_TASKS_FROZEN) {
+ case CPU_STARTING:
+ msm_cache_erp_irq_init(NULL);
+ enable_erp_irq_callback(NULL);
+ break;
+ case CPU_DYING:
+ disable_erp_irq_callback(NULL);
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+static struct notifier_block cache_erp_cpu_notifier = {
+ .notifier_call = cache_erp_cpu_callback,
+};
+
+static int msm_cache_erp_probe(struct platform_device *pdev)
+{
+ int i, ret = 0;
+ struct resource *r;
+
+ dev_dbg(&pdev->dev, "enter\n");
+
+ /* L3 */
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ hml3_base = devm_ioremap_resource(&pdev->dev, r);
+ if (IS_ERR(hml3_base)) {
+ dev_err(&pdev->dev, "failed to ioremap (0x%pK)\n", hml3_base);
+ return PTR_ERR(hml3_base);
+ }
+
+ for (i = 0; i <= IRQ_L3; i++) {
+ r = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ erp_irq_names[i]);
+ if (!r) {
+ dev_err(&pdev->dev, "failed to get %s\n",
+ erp_irq_names[i]);
+ return -ENODEV;
+ }
+ erp_irqs[i] = r->start;
+ }
+
+ msm_cache_erp_l3_init();
+
+ /* L0/L1 erp irq per cpu */
+ dev_info(&pdev->dev, "Registering for L1 error interrupts\n");
+ ret = request_percpu_irq(erp_irqs[IRQ_L1], msm_l1_erp_irq,
+ erp_irq_names[IRQ_L1], &msm_l1_erp_stats);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request L0/L1 ERP irq %s (%d)\n",
+ erp_irq_names[IRQ_L1], ret);
+ return ret;
+ } else {
+ dev_dbg(&pdev->dev, "requested L0/L1 ERP irq %s\n",
+ erp_irq_names[IRQ_L1]);
+ }
+
+ get_online_cpus();
+ register_hotcpu_notifier(&cache_erp_cpu_notifier);
+ cpu_pm_register_notifier(&cache_erp_cpu_pm_notifier);
+
+ /* Perform L1/L2 cache error detection init on online cpus */
+ on_each_cpu(msm_cache_erp_irq_init, NULL, 1);
+ /* Enable irqs */
+ on_each_cpu(enable_erp_irq_callback, NULL, 1);
+ put_online_cpus();
+
+ /* L2 erp irq per cluster */
+ dev_info(&pdev->dev, "Registering for L2 error interrupts\n");
+ for (i = IRQ_L2_INFO0; i <= IRQ_L2_ERR1; i++) {
+ ret = devm_request_irq(&pdev->dev, erp_irqs[i],
+ msm_l2_erp_irq,
+ IRQF_ONESHOT |
+ IRQF_TRIGGER_HIGH,
+ erp_irq_names[i], NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request irq %s (%d)\n",
+ erp_irq_names[i], ret);
+ goto cleanup;
+ }
+ }
+
+ /* L3 erp irq */
+ dev_info(&pdev->dev, "Registering for L3 error interrupts\n");
+ ret = devm_request_irq(&pdev->dev, erp_irqs[IRQ_L3], msm_l3_erp_irq,
+ IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
+ erp_irq_names[IRQ_L3], NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request L3 irq %s (%d)\n",
+ erp_irq_names[IRQ_L3], ret);
+ goto cleanup;
+ }
+
+ return 0;
+
+cleanup:
+ free_percpu_irq(erp_irqs[IRQ_L1], NULL);
+ return ret;
+}
+
+static void msm_m4m_erp_irq_init(void)
+{
+ writel_relaxed(M4M_INT_CTRL_IRQ_EN, m4m_base + M4M_INT_CTRL);
+ writel_relaxed(0, m4m_base + M4M_ERR_CTRL);
+}
+
+static int msm_m4m_erp_m4m_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct resource *r;
+
+ dev_dbg(&pdev->dev, "enter\n");
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ m4m_base = devm_ioremap_resource(&pdev->dev, r);
+ if (IS_ERR(m4m_base)) {
+ dev_err(&pdev->dev, "failed to ioremap (0x%pK)\n", m4m_base);
+ return PTR_ERR(m4m_base);
+ }
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ erp_irq_names[IRQ_M4M]);
+ if (!r) {
+ dev_err(&pdev->dev, "failed to get %s\n",
+ erp_irq_names[IRQ_M4M]);
+ ret = -ENODEV;
+ goto exit;
+ }
+ erp_irqs[IRQ_M4M] = r->start;
+
+ dev_info(&pdev->dev, "Registering for M4M error interrupts\n");
+ ret = devm_request_irq(&pdev->dev, erp_irqs[IRQ_M4M],
+ msm_m4m_erp_irq,
+ IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
+ erp_irq_names[IRQ_M4M], NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request irq %s (%d)\n",
+ erp_irq_names[IRQ_M4M], ret);
+ goto exit;
+ }
+
+ msm_m4m_erp_irq_init();
+
+exit:
+ return ret;
+}
+
+static struct of_device_id cache_erp_dt_ids[] = {
+ { .compatible = "qcom,kryo_cache_erp64", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cache_erp_dt_ids);
+
+static struct platform_driver msm_cache_erp_driver = {
+ .probe = msm_cache_erp_probe,
+ .driver = {
+ .name = "msm_cache_erp64",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(cache_erp_dt_ids),
+ },
+};
+
+static struct of_device_id m4m_erp_dt_ids[] = {
+ { .compatible = "qcom,m4m_erp", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, m4m_erp_dt_ids);
+static struct platform_driver msm_m4m_erp_driver = {
+ .probe = msm_m4m_erp_m4m_probe,
+ .driver = {
+ .name = "msm_m4m_erp",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(m4m_erp_dt_ids),
+ },
+};
+
+static int __init msm_cache_erp_init(void)
+{
+ int r;
+
+ r = platform_driver_register(&msm_cache_erp_driver);
+ if (!r)
+ r = platform_driver_register(&msm_m4m_erp_driver);
+ if (r)
+ pr_err("failed to register driver %d\n", r);
+ return r;
+}
+
+arch_initcall(msm_cache_erp_init);
diff --git a/drivers/soc/qcom/scm-boot.c b/drivers/soc/qcom/scm-boot.c
index 369fb27ff447..f3e96f9afa12 100644
--- a/drivers/soc/qcom/scm-boot.c
+++ b/drivers/soc/qcom/scm-boot.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010, 2014, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2010, 2014, 2016, 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
@@ -24,11 +24,20 @@ int scm_set_boot_addr(phys_addr_t addr, unsigned int flags)
u32 flags;
u32 addr;
} cmd;
+ struct scm_desc desc = {0};
+
+ if (!is_scm_armv8()) {
+ cmd.addr = addr;
+ cmd.flags = flags;
+ return scm_call(SCM_SVC_BOOT, SCM_BOOT_ADDR,
+ &cmd, sizeof(cmd), NULL, 0);
+ }
+
+ desc.args[0] = addr;
+ desc.args[1] = flags;
+ desc.arginfo = SCM_ARGS(2);
- cmd.addr = addr;
- cmd.flags = flags;
- return scm_call(SCM_SVC_BOOT, SCM_BOOT_ADDR,
- &cmd, sizeof(cmd), NULL, 0);
+ return scm_call2(SCM_SIP_FNID(SCM_SVC_BOOT, SCM_BOOT_ADDR), &desc);
}
EXPORT_SYMBOL(scm_set_boot_addr);
diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c
index c1d8748a5d08..b9903fe86f60 100644
--- a/drivers/soc/qcom/socinfo.c
+++ b/drivers/soc/qcom/socinfo.c
@@ -65,6 +65,7 @@ enum {
HW_PLATFORM_RCM = 21,
HW_PLATFORM_STP = 23,
HW_PLATFORM_SBC = 24,
+ HW_PLATFORM_ADP = 25,
HW_PLATFORM_INVALID
};
@@ -85,6 +86,7 @@ const char *hw_platform[] = {
[HW_PLATFORM_DTV] = "DTV",
[HW_PLATFORM_STP] = "STP",
[HW_PLATFORM_SBC] = "SBC",
+ [HW_PLATFORM_ADP] = "ADP",
};
enum {
@@ -111,6 +113,22 @@ const char *qrd_hw_platform_subtype[] = {
};
enum {
+ PLATFORM_SUBTYPE_MOJAVE_V1 = 0x0,
+ PLATFORM_SUBTYPE_MMX = 0x1,
+ PLATFORM_SUBTYPE_MOJAVE_FULL_V2 = 0x2,
+ PLATFORM_SUBTYPE_MOJAVE_BARE_V2 = 0x3,
+ PLATFORM_SUBTYPE_ADP_INVALID,
+};
+
+const char *adp_hw_platform_subtype[] = {
+ [PLATFORM_SUBTYPE_MOJAVE_V1] = "MOJAVE_V1",
+ [PLATFORM_SUBTYPE_MMX] = "MMX",
+ [PLATFORM_SUBTYPE_MOJAVE_FULL_V2] = "_MOJAVE_V2_FULL",
+ [PLATFORM_SUBTYPE_MOJAVE_BARE_V2] = "_MOJAVE_V2_BARE",
+ [PLATFORM_SUBTYPE_ADP_INVALID] = "INVALID",
+};
+
+enum {
PLATFORM_SUBTYPE_UNKNOWN = 0x0,
PLATFORM_SUBTYPE_CHARM = 0x1,
PLATFORM_SUBTYPE_STRANGE = 0x2,
@@ -514,11 +532,13 @@ static struct msm_soc_info cpu_of_id[] = {
/* 8996 IDs */
[246] = {MSM_CPU_8996, "MSM8996"},
- [310] = {MSM_CPU_8996, "MSM8996"},
- [311] = {MSM_CPU_8996, "APQ8096"},
[291] = {MSM_CPU_8996, "APQ8096"},
[305] = {MSM_CPU_8996, "MSM8996pro"},
+ [310] = {MSM_CPU_8996, "MSM8996"},
+ [311] = {MSM_CPU_8996, "APQ8096"},
[312] = {MSM_CPU_8996, "APQ8096pro"},
+ [315] = {MSM_CPU_8996, "MSM8996pro"},
+ [316] = {MSM_CPU_8996, "APQ8096pro"},
/* 8976 ID */
[266] = {MSM_CPU_8976, "MSM8976"},
@@ -804,6 +824,14 @@ msm_get_platform_subtype(struct device *dev,
}
return snprintf(buf, PAGE_SIZE, "%-.32s\n",
qrd_hw_platform_subtype[hw_subtype]);
+ }
+ if (socinfo_get_platform_type() == HW_PLATFORM_ADP) {
+ if (hw_subtype >= PLATFORM_SUBTYPE_ADP_INVALID) {
+ pr_err("Invalid hardware platform sub type for adp found\n");
+ hw_subtype = PLATFORM_SUBTYPE_ADP_INVALID;
+ }
+ return snprintf(buf, PAGE_SIZE, "%-.32s\n",
+ adp_hw_platform_subtype[hw_subtype]);
} else {
if (hw_subtype >= PLATFORM_SUBTYPE_INVALID) {
pr_err("Invalid hardware platform subtype\n");
@@ -1225,10 +1253,6 @@ static void * __init setup_dummy_socinfo(void)
dummy_socinfo.id = 246;
strlcpy(dummy_socinfo.build_id, "msm8996 - ",
sizeof(dummy_socinfo.build_id));
- } else if (early_machine_is_msm8996_auto()) {
- dummy_socinfo.id = 310;
- strlcpy(dummy_socinfo.build_id, "msm8996-auto - ",
- sizeof(dummy_socinfo.build_id));
} else if (early_machine_is_msm8929()) {
dummy_socinfo.id = 268;
strlcpy(dummy_socinfo.build_id, "msm8929 - ",
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index f7a7fc21be8a..e8f9172880c4 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -268,3 +268,13 @@ config USB_CHAOSKEY
To compile this driver as a module, choose M here: the
module will be called chaoskey.
+
+config USB_QTI_KS_BRIDGE
+ tristate "USB QTI kick start bridge"
+ depends on USB
+ help
+ Say Y here if you have a QTI modem device connected via USB that
+ will be bridged in kernel space. This driver works as a bridge to pass
+ boot images, ram-dumps and efs sync.
+ To compile this driver as a module, choose M here: the module
+ will be called ks_bridge. If unsure, choose N.
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 45fd4ac39d3e..616902bce450 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -29,3 +29,5 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o
obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/
obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o
+
+obj-$(CONFIG_USB_QTI_KS_BRIDGE) += ks_bridge.o
diff --git a/drivers/usb/misc/ks_bridge.c b/drivers/usb/misc/ks_bridge.c
new file mode 100644
index 000000000000..35f652c281bb
--- /dev/null
+++ b/drivers/usb/misc/ks_bridge.c
@@ -0,0 +1,1105 @@
+/*
+ * Copyright (c) 2012-2014, 2017, 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.
+ */
+
+/* add additional information to our printk's */
+#define pr_fmt(fmt) "%s: " fmt "\n", __func__
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/platform_device.h>
+#include <linux/ratelimit.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/cdev.h>
+#include <linux/list.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+
+#define DRIVER_DESC "USB host ks bridge driver"
+
+enum bus_id {
+ BUS_HSIC,
+ BUS_USB,
+ BUS_UNDEF,
+};
+
+#define BUSNAME_LEN 20
+
+static enum bus_id str_to_busid(const char *name)
+{
+ if (!strncasecmp("msm_hsic_host", name, BUSNAME_LEN))
+ return BUS_HSIC;
+ if (!strncasecmp("msm_ehci_host.0", name, BUSNAME_LEN))
+ return BUS_USB;
+ if (!strncasecmp("xhci-hcd.0.auto", name, BUSNAME_LEN))
+ return BUS_USB;
+
+ return BUS_UNDEF;
+}
+
+struct data_pkt {
+ int n_read;
+ char *buf;
+ size_t len;
+ struct list_head list;
+ void *ctxt;
+};
+
+#define FILE_OPENED BIT(0)
+#define USB_DEV_CONNECTED BIT(1)
+#define NO_RX_REQS 10
+#define NO_BRIDGE_INSTANCES 4
+#define EFS_HSIC_BRIDGE_INDEX 2
+#define EFS_USB_BRIDGE_INDEX 3
+#define MAX_DATA_PKT_SIZE 16384
+#define PENDING_URB_TIMEOUT 10
+
+struct ksb_dev_info {
+ const char *name;
+};
+
+struct ks_bridge {
+ char *name;
+ spinlock_t lock;
+ struct workqueue_struct *wq;
+ struct work_struct to_mdm_work;
+ struct work_struct start_rx_work;
+ struct list_head to_mdm_list;
+ struct list_head to_ks_list;
+ wait_queue_head_t ks_wait_q;
+ wait_queue_head_t pending_urb_wait;
+ atomic_t tx_pending_cnt;
+ atomic_t rx_pending_cnt;
+
+ struct ksb_dev_info id_info;
+
+ /* cdev interface */
+ dev_t cdev_start_no;
+ struct cdev cdev;
+ struct class *class;
+ struct device *device;
+
+ /* usb specific */
+ struct usb_device *udev;
+ struct usb_interface *ifc;
+ __u8 in_epAddr;
+ __u8 out_epAddr;
+ unsigned int in_pipe;
+ unsigned int out_pipe;
+ struct usb_anchor submitted;
+
+ unsigned long flags;
+
+ /* to handle INT IN ep */
+ unsigned int period;
+
+#define DBG_MSG_LEN 40
+#define DBG_MAX_MSG 500
+ unsigned int dbg_idx;
+ rwlock_t dbg_lock;
+
+ char (dbgbuf[DBG_MAX_MSG])[DBG_MSG_LEN]; /* buffer */
+};
+
+struct ks_bridge *__ksb[NO_BRIDGE_INSTANCES];
+
+/* by default debugging is enabled */
+static unsigned int enable_dbg = 1;
+module_param(enable_dbg, uint, S_IRUGO | S_IWUSR);
+
+static void
+dbg_log_event(struct ks_bridge *ksb, char *event, int d1, int d2)
+{
+ unsigned long flags;
+ unsigned long long t;
+ unsigned long nanosec;
+
+ if (!enable_dbg)
+ return;
+
+ write_lock_irqsave(&ksb->dbg_lock, flags);
+ t = cpu_clock(smp_processor_id());
+ nanosec = do_div(t, 1000000000)/1000;
+ scnprintf(ksb->dbgbuf[ksb->dbg_idx], DBG_MSG_LEN, "%5lu.%06lu:%s:%x:%x",
+ (unsigned long)t, nanosec, event, d1, d2);
+
+ ksb->dbg_idx++;
+ ksb->dbg_idx = ksb->dbg_idx % DBG_MAX_MSG;
+ write_unlock_irqrestore(&ksb->dbg_lock, flags);
+}
+
+static
+struct data_pkt *ksb_alloc_data_pkt(size_t count, gfp_t flags, void *ctxt)
+{
+ struct data_pkt *pkt;
+
+ pkt = kzalloc(sizeof(struct data_pkt), flags);
+ if (!pkt)
+ return ERR_PTR(-ENOMEM);
+
+ pkt->buf = kmalloc(count, flags);
+ if (!pkt->buf) {
+ kfree(pkt);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ pkt->len = count;
+ INIT_LIST_HEAD(&pkt->list);
+ pkt->ctxt = ctxt;
+
+ return pkt;
+}
+
+static void ksb_free_data_pkt(struct data_pkt *pkt)
+{
+ kfree(pkt->buf);
+ kfree(pkt);
+}
+
+
+static void
+submit_one_urb(struct ks_bridge *ksb, gfp_t flags, struct data_pkt *pkt);
+static ssize_t ksb_fs_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ int ret;
+ unsigned long flags;
+ struct ks_bridge *ksb = fp->private_data;
+ struct data_pkt *pkt = NULL;
+ size_t space, copied;
+
+read_start:
+ if (!test_bit(USB_DEV_CONNECTED, &ksb->flags))
+ return -ENODEV;
+
+ spin_lock_irqsave(&ksb->lock, flags);
+ if (list_empty(&ksb->to_ks_list)) {
+ spin_unlock_irqrestore(&ksb->lock, flags);
+ ret = wait_event_interruptible(ksb->ks_wait_q,
+ !list_empty(&ksb->to_ks_list) ||
+ !test_bit(USB_DEV_CONNECTED, &ksb->flags));
+ if (ret < 0)
+ return ret;
+
+ goto read_start;
+ }
+
+ space = count;
+ copied = 0;
+ while (!list_empty(&ksb->to_ks_list) && space &&
+ test_bit(USB_DEV_CONNECTED, &ksb->flags)) {
+ size_t len;
+
+ pkt = list_first_entry(&ksb->to_ks_list, struct data_pkt, list);
+ list_del_init(&pkt->list);
+ len = min_t(size_t, space, pkt->len - pkt->n_read);
+ spin_unlock_irqrestore(&ksb->lock, flags);
+
+ ret = copy_to_user(buf + copied, pkt->buf + pkt->n_read, len);
+ if (ret) {
+ dev_err(ksb->device,
+ "copy_to_user failed err:%d\n", ret);
+ ksb_free_data_pkt(pkt);
+ return -EFAULT;
+ }
+
+ pkt->n_read += len;
+ space -= len;
+ copied += len;
+
+ if (pkt->n_read == pkt->len) {
+ /*
+ * re-init the packet and queue it
+ * for more data.
+ */
+ pkt->n_read = 0;
+ pkt->len = MAX_DATA_PKT_SIZE;
+ submit_one_urb(ksb, GFP_KERNEL, pkt);
+ pkt = NULL;
+ }
+ spin_lock_irqsave(&ksb->lock, flags);
+ }
+
+ /* put the partial packet back in the list */
+ if (!space && pkt && pkt->n_read != pkt->len) {
+ if (test_bit(USB_DEV_CONNECTED, &ksb->flags))
+ list_add(&pkt->list, &ksb->to_ks_list);
+ else
+ ksb_free_data_pkt(pkt);
+ }
+ spin_unlock_irqrestore(&ksb->lock, flags);
+
+ dbg_log_event(ksb, "KS_READ", copied, 0);
+
+ dev_dbg(ksb->device, "count:%zu space:%zu copied:%zu", count,
+ space, copied);
+
+ return copied;
+}
+
+static void ksb_tx_cb(struct urb *urb)
+{
+ struct data_pkt *pkt = urb->context;
+ struct ks_bridge *ksb = pkt->ctxt;
+
+ dbg_log_event(ksb, "C TX_URB", urb->status, 0);
+ dev_dbg(&ksb->udev->dev, "status:%d", urb->status);
+
+ if (test_bit(USB_DEV_CONNECTED, &ksb->flags))
+ usb_autopm_put_interface_async(ksb->ifc);
+
+ if (urb->status < 0)
+ pr_err_ratelimited("%s: urb failed with err:%d",
+ ksb->id_info.name, urb->status);
+
+ ksb_free_data_pkt(pkt);
+
+ atomic_dec(&ksb->tx_pending_cnt);
+ wake_up(&ksb->pending_urb_wait);
+}
+
+static void ksb_tomdm_work(struct work_struct *w)
+{
+ struct ks_bridge *ksb = container_of(w, struct ks_bridge, to_mdm_work);
+ struct data_pkt *pkt;
+ unsigned long flags;
+ struct urb *urb;
+ int ret;
+
+ spin_lock_irqsave(&ksb->lock, flags);
+ while (!list_empty(&ksb->to_mdm_list)
+ && test_bit(USB_DEV_CONNECTED, &ksb->flags)) {
+ pkt = list_first_entry(&ksb->to_mdm_list,
+ struct data_pkt, list);
+ list_del_init(&pkt->list);
+ spin_unlock_irqrestore(&ksb->lock, flags);
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ dbg_log_event(ksb, "TX_URB_MEM_FAIL", -ENOMEM, 0);
+ pr_err_ratelimited("%s: unable to allocate urb",
+ ksb->id_info.name);
+ ksb_free_data_pkt(pkt);
+ return;
+ }
+
+ ret = usb_autopm_get_interface(ksb->ifc);
+ if (ret < 0 && ret != -EAGAIN && ret != -EACCES) {
+ dbg_log_event(ksb, "TX_URB_AUTOPM_FAIL", ret, 0);
+ pr_err_ratelimited("%s: autopm_get failed:%d",
+ ksb->id_info.name, ret);
+ usb_free_urb(urb);
+ ksb_free_data_pkt(pkt);
+ return;
+ }
+ usb_fill_bulk_urb(urb, ksb->udev, ksb->out_pipe,
+ pkt->buf, pkt->len, ksb_tx_cb, pkt);
+ usb_anchor_urb(urb, &ksb->submitted);
+
+ dbg_log_event(ksb, "S TX_URB", pkt->len, 0);
+
+ atomic_inc(&ksb->tx_pending_cnt);
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret) {
+ dev_err(&ksb->udev->dev, "out urb submission failed");
+ usb_unanchor_urb(urb);
+ usb_free_urb(urb);
+ ksb_free_data_pkt(pkt);
+ usb_autopm_put_interface(ksb->ifc);
+ atomic_dec(&ksb->tx_pending_cnt);
+ wake_up(&ksb->pending_urb_wait);
+ return;
+ }
+
+ usb_free_urb(urb);
+
+ spin_lock_irqsave(&ksb->lock, flags);
+ }
+ spin_unlock_irqrestore(&ksb->lock, flags);
+}
+
+static ssize_t ksb_fs_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ int ret;
+ struct data_pkt *pkt;
+ unsigned long flags;
+ struct ks_bridge *ksb = fp->private_data;
+
+ if (!test_bit(USB_DEV_CONNECTED, &ksb->flags))
+ return -ENODEV;
+
+ if (count > MAX_DATA_PKT_SIZE)
+ count = MAX_DATA_PKT_SIZE;
+
+ pkt = ksb_alloc_data_pkt(count, GFP_KERNEL, ksb);
+ if (IS_ERR(pkt)) {
+ dev_err(ksb->device,
+ "unable to allocate data packet");
+ return PTR_ERR(pkt);
+ }
+
+ ret = copy_from_user(pkt->buf, buf, count);
+ if (ret) {
+ dev_err(ksb->device,
+ "copy_from_user failed: err:%d", ret);
+ ksb_free_data_pkt(pkt);
+ return ret;
+ }
+
+ spin_lock_irqsave(&ksb->lock, flags);
+ list_add_tail(&pkt->list, &ksb->to_mdm_list);
+ spin_unlock_irqrestore(&ksb->lock, flags);
+
+ queue_work(ksb->wq, &ksb->to_mdm_work);
+
+ dbg_log_event(ksb, "KS_WRITE", count, 0);
+
+ return count;
+}
+
+static int ksb_fs_open(struct inode *ip, struct file *fp)
+{
+ struct ks_bridge *ksb =
+ container_of(ip->i_cdev, struct ks_bridge, cdev);
+
+ if (IS_ERR(ksb)) {
+ pr_err("ksb device not found");
+ return -ENODEV;
+ }
+
+ dev_dbg(ksb->device, ":%s", ksb->id_info.name);
+ dbg_log_event(ksb, "FS-OPEN", 0, 0);
+
+ fp->private_data = ksb;
+ set_bit(FILE_OPENED, &ksb->flags);
+
+ if (test_bit(USB_DEV_CONNECTED, &ksb->flags))
+ queue_work(ksb->wq, &ksb->start_rx_work);
+
+ return 0;
+}
+
+static unsigned int ksb_fs_poll(struct file *file, poll_table *wait)
+{
+ struct ks_bridge *ksb = file->private_data;
+ unsigned long flags;
+ int ret = 0;
+
+ if (!test_bit(USB_DEV_CONNECTED, &ksb->flags))
+ return POLLERR;
+
+ poll_wait(file, &ksb->ks_wait_q, wait);
+ if (!test_bit(USB_DEV_CONNECTED, &ksb->flags))
+ return POLLERR;
+
+ spin_lock_irqsave(&ksb->lock, flags);
+ if (!list_empty(&ksb->to_ks_list))
+ ret = POLLIN | POLLRDNORM;
+ spin_unlock_irqrestore(&ksb->lock, flags);
+
+ return ret;
+}
+
+static int ksb_fs_release(struct inode *ip, struct file *fp)
+{
+ struct ks_bridge *ksb = fp->private_data;
+
+ if (test_bit(USB_DEV_CONNECTED, &ksb->flags))
+ dev_dbg(ksb->device, ":%s", ksb->id_info.name);
+ dbg_log_event(ksb, "FS-RELEASE", 0, 0);
+
+ clear_bit(FILE_OPENED, &ksb->flags);
+ fp->private_data = NULL;
+
+ return 0;
+}
+
+static const struct file_operations ksb_fops = {
+ .owner = THIS_MODULE,
+ .read = ksb_fs_read,
+ .write = ksb_fs_write,
+ .open = ksb_fs_open,
+ .release = ksb_fs_release,
+ .poll = ksb_fs_poll,
+};
+
+static struct ksb_dev_info ksb_fboot_dev[] = {
+ {
+ .name = "ks_hsic_bridge",
+ },
+ {
+ .name = "ks_usb_bridge",
+ },
+};
+
+static struct ksb_dev_info ksb_efs_hsic_dev = {
+ .name = "efs_hsic_bridge",
+};
+
+static struct ksb_dev_info ksb_efs_usb_dev = {
+ .name = "efs_usb_bridge",
+};
+static const struct usb_device_id ksb_usb_ids[] = {
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9008, 0),
+ .driver_info = (unsigned long)&ksb_fboot_dev, },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9025, 0), },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9091, 0), },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x901D, 0), },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x900E, 0), },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9048, 2),
+ .driver_info = (unsigned long)&ksb_efs_hsic_dev, },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x904C, 2),
+ .driver_info = (unsigned long)&ksb_efs_hsic_dev, },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9075, 2),
+ .driver_info = (unsigned long)&ksb_efs_hsic_dev, },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9079, 2),
+ .driver_info = (unsigned long)&ksb_efs_usb_dev, },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x908A, 2),
+ .driver_info = (unsigned long)&ksb_efs_hsic_dev, },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x908E, 3),
+ .driver_info = (unsigned long)&ksb_efs_hsic_dev, },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909C, 2),
+ .driver_info = (unsigned long)&ksb_efs_hsic_dev, },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909D, 2),
+ .driver_info = (unsigned long)&ksb_efs_hsic_dev, },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909E, 3),
+ .driver_info = (unsigned long)&ksb_efs_hsic_dev, },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909F, 2),
+ .driver_info = (unsigned long)&ksb_efs_hsic_dev, },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x90A0, 2),
+ .driver_info = (unsigned long)&ksb_efs_hsic_dev, },
+ { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x90A4, 3),
+ .driver_info = (unsigned long)&ksb_efs_hsic_dev, },
+
+ {} /* terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, ksb_usb_ids);
+
+static void ksb_rx_cb(struct urb *urb);
+static void
+submit_one_urb(struct ks_bridge *ksb, gfp_t flags, struct data_pkt *pkt)
+{
+ struct urb *urb;
+ int ret;
+
+ urb = usb_alloc_urb(0, flags);
+ if (!urb) {
+ dev_err(&ksb->udev->dev, "unable to allocate urb");
+ ksb_free_data_pkt(pkt);
+ return;
+ }
+
+ if (ksb->period)
+ usb_fill_int_urb(urb, ksb->udev, ksb->in_pipe,
+ pkt->buf, pkt->len,
+ ksb_rx_cb, pkt, ksb->period);
+ else
+ usb_fill_bulk_urb(urb, ksb->udev, ksb->in_pipe,
+ pkt->buf, pkt->len,
+ ksb_rx_cb, pkt);
+
+ usb_anchor_urb(urb, &ksb->submitted);
+
+ if (!test_bit(USB_DEV_CONNECTED, &ksb->flags)) {
+ usb_unanchor_urb(urb);
+ usb_free_urb(urb);
+ ksb_free_data_pkt(pkt);
+ return;
+ }
+
+ atomic_inc(&ksb->rx_pending_cnt);
+ ret = usb_submit_urb(urb, flags);
+ if (ret) {
+ dev_err(&ksb->udev->dev, "in urb submission failed");
+ usb_unanchor_urb(urb);
+ usb_free_urb(urb);
+ ksb_free_data_pkt(pkt);
+ atomic_dec(&ksb->rx_pending_cnt);
+ wake_up(&ksb->pending_urb_wait);
+ return;
+ }
+
+ dbg_log_event(ksb, "S RX_URB", pkt->len, 0);
+
+ usb_free_urb(urb);
+}
+static void ksb_rx_cb(struct urb *urb)
+{
+ struct data_pkt *pkt = urb->context;
+ struct ks_bridge *ksb = pkt->ctxt;
+ bool wakeup = true;
+
+ dbg_log_event(ksb, "C RX_URB", urb->status, urb->actual_length);
+
+ dev_dbg(&ksb->udev->dev, "status:%d actual:%d", urb->status,
+ urb->actual_length);
+
+ /*non zero len of data received while unlinking urb*/
+ if (urb->status == -ENOENT && (urb->actual_length > 0)) {
+ /*
+ * If we wakeup the reader process now, it may
+ * queue the URB before its reject flag gets
+ * cleared.
+ */
+ wakeup = false;
+ goto add_to_list;
+ }
+
+ if (urb->status < 0) {
+ if (urb->status != -ESHUTDOWN && urb->status != -ENOENT
+ && urb->status != -EPROTO)
+ pr_err_ratelimited("%s: urb failed with err:%d",
+ ksb->id_info.name, urb->status);
+
+ if (!urb->actual_length) {
+ ksb_free_data_pkt(pkt);
+ goto done;
+ }
+ }
+
+ usb_mark_last_busy(ksb->udev);
+
+ if (urb->actual_length == 0) {
+ submit_one_urb(ksb, GFP_ATOMIC, pkt);
+ goto done;
+ }
+
+add_to_list:
+ spin_lock(&ksb->lock);
+ pkt->len = urb->actual_length;
+ list_add_tail(&pkt->list, &ksb->to_ks_list);
+ spin_unlock(&ksb->lock);
+ /* wake up read thread */
+ if (wakeup)
+ wake_up(&ksb->ks_wait_q);
+done:
+ atomic_dec(&ksb->rx_pending_cnt);
+ wake_up(&ksb->pending_urb_wait);
+}
+
+static void ksb_start_rx_work(struct work_struct *w)
+{
+ struct ks_bridge *ksb =
+ container_of(w, struct ks_bridge, start_rx_work);
+ struct data_pkt *pkt;
+ struct urb *urb;
+ int i = 0;
+ int ret;
+ bool put = true;
+
+ ret = usb_autopm_get_interface(ksb->ifc);
+ if (ret < 0) {
+ if (ret != -EAGAIN && ret != -EACCES) {
+ pr_err_ratelimited("%s: autopm_get failed:%d",
+ ksb->id_info.name, ret);
+ return;
+ }
+ put = false;
+ }
+ for (i = 0; i < NO_RX_REQS; i++) {
+
+ if (!test_bit(USB_DEV_CONNECTED, &ksb->flags))
+ break;
+
+ pkt = ksb_alloc_data_pkt(MAX_DATA_PKT_SIZE, GFP_KERNEL, ksb);
+ if (IS_ERR(pkt)) {
+ dev_err(&ksb->udev->dev, "unable to allocate data pkt");
+ break;
+ }
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ dev_err(&ksb->udev->dev, "unable to allocate urb");
+ ksb_free_data_pkt(pkt);
+ break;
+ }
+
+ if (ksb->period)
+ usb_fill_int_urb(urb, ksb->udev, ksb->in_pipe,
+ pkt->buf, pkt->len,
+ ksb_rx_cb, pkt, ksb->period);
+ else
+ usb_fill_bulk_urb(urb, ksb->udev, ksb->in_pipe,
+ pkt->buf, pkt->len,
+ ksb_rx_cb, pkt);
+
+ usb_anchor_urb(urb, &ksb->submitted);
+
+ dbg_log_event(ksb, "S RX_URB", pkt->len, 0);
+
+ atomic_inc(&ksb->rx_pending_cnt);
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret) {
+ dev_err(&ksb->udev->dev, "in urb submission failed");
+ usb_unanchor_urb(urb);
+ usb_free_urb(urb);
+ ksb_free_data_pkt(pkt);
+ atomic_dec(&ksb->rx_pending_cnt);
+ wake_up(&ksb->pending_urb_wait);
+ break;
+ }
+
+ usb_free_urb(urb);
+ }
+ if (put)
+ usb_autopm_put_interface_async(ksb->ifc);
+}
+
+static int
+ksb_usb_probe(struct usb_interface *ifc, const struct usb_device_id *id)
+{
+ __u8 ifc_num, ifc_count, ksb_port_num;
+ struct usb_host_interface *ifc_desc;
+ struct usb_endpoint_descriptor *ep_desc;
+ int i;
+ struct ks_bridge *ksb;
+ unsigned long flags;
+ struct data_pkt *pkt;
+ struct ksb_dev_info *mdev, *fbdev;
+ struct usb_device *udev;
+ unsigned int bus_id;
+ int ret;
+ bool free_mdev = false;
+
+ ifc_num = ifc->cur_altsetting->desc.bInterfaceNumber;
+
+ udev = interface_to_usbdev(ifc);
+ ifc_count = udev->actconfig->desc.bNumInterfaces;
+ fbdev = mdev = (struct ksb_dev_info *)id->driver_info;
+
+ bus_id = str_to_busid(udev->bus->bus_name);
+ if (bus_id == BUS_UNDEF) {
+ dev_err(&udev->dev, "unknown usb bus %s, probe failed\n",
+ udev->bus->bus_name);
+ return -ENODEV;
+ }
+
+ switch (id->idProduct) {
+ case 0x900E:
+ case 0x9025:
+ case 0x9091:
+ case 0x901D:
+ /* 1-1 mapping between ksb and udev port which starts with 1 */
+ ksb_port_num = udev->portnum - 1;
+ dev_dbg(&udev->dev, "ifc_count: %u, port_num:%u\n", ifc_count,
+ ksb_port_num);
+ if (ifc_count > 1)
+ return -ENODEV;
+ if (ksb_port_num >= NO_BRIDGE_INSTANCES) {
+ dev_err(&udev->dev, "port-num:%u invalid. Try first\n",
+ ksb_port_num);
+ ksb_port_num = 0;
+ }
+ ksb = __ksb[ksb_port_num];
+ if (ksb->ifc) {
+ dev_err(&udev->dev, "port already in use\n");
+ return -ENODEV;
+ }
+ mdev = kzalloc(sizeof(struct ksb_dev_info), GFP_KERNEL);
+ if (!mdev)
+ return -ENOMEM;
+ free_mdev = true;
+ mdev->name = ksb->name;
+ break;
+ case 0x9008:
+ ksb = __ksb[bus_id];
+ mdev = &fbdev[bus_id];
+ break;
+ case 0x9048:
+ case 0x904C:
+ case 0x9075:
+ case 0x908A:
+ case 0x908E:
+ case 0x90A0:
+ case 0x909C:
+ case 0x909D:
+ case 0x909E:
+ case 0x909F:
+ case 0x90A4:
+ ksb = __ksb[EFS_HSIC_BRIDGE_INDEX];
+ break;
+ case 0x9079:
+ if (ifc_num != 2)
+ return -ENODEV;
+ ksb = __ksb[EFS_USB_BRIDGE_INDEX];
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ if (!ksb) {
+ pr_err("ksb is not initialized");
+ return -ENODEV;
+ }
+
+ ksb->udev = usb_get_dev(interface_to_usbdev(ifc));
+ ksb->ifc = ifc;
+ ifc_desc = ifc->cur_altsetting;
+ ksb->id_info = *mdev;
+
+ for (i = 0; i < ifc_desc->desc.bNumEndpoints; i++) {
+ ep_desc = &ifc_desc->endpoint[i].desc;
+
+ if (!ksb->in_epAddr && (usb_endpoint_is_bulk_in(ep_desc))) {
+ ksb->in_epAddr = ep_desc->bEndpointAddress;
+ ksb->period = 0;
+ }
+
+ if (!ksb->in_epAddr && (usb_endpoint_is_int_in(ep_desc))) {
+ ksb->in_epAddr = ep_desc->bEndpointAddress;
+ ksb->period = ep_desc->bInterval;
+ }
+
+ if (!ksb->out_epAddr && usb_endpoint_is_bulk_out(ep_desc))
+ ksb->out_epAddr = ep_desc->bEndpointAddress;
+ }
+
+ if (!(ksb->in_epAddr && ksb->out_epAddr)) {
+ dev_err(&udev->dev,
+ "could not find bulk in and bulk out endpoints");
+ usb_put_dev(ksb->udev);
+ ksb->ifc = NULL;
+ if (free_mdev)
+ kfree(mdev);
+ return -ENODEV;
+ }
+
+ ksb->in_pipe = ksb->period ?
+ usb_rcvintpipe(ksb->udev, ksb->in_epAddr) :
+ usb_rcvbulkpipe(ksb->udev, ksb->in_epAddr);
+
+ ksb->out_pipe = usb_sndbulkpipe(ksb->udev, ksb->out_epAddr);
+
+ usb_set_intfdata(ifc, ksb);
+ set_bit(USB_DEV_CONNECTED, &ksb->flags);
+ atomic_set(&ksb->tx_pending_cnt, 0);
+ atomic_set(&ksb->rx_pending_cnt, 0);
+
+ dbg_log_event(ksb, "PID-ATT", id->idProduct, 0);
+
+ /*free up stale buffers if any from previous disconnect*/
+ spin_lock_irqsave(&ksb->lock, flags);
+ while (!list_empty(&ksb->to_ks_list)) {
+ pkt = list_first_entry(&ksb->to_ks_list,
+ struct data_pkt, list);
+ list_del_init(&pkt->list);
+ ksb_free_data_pkt(pkt);
+ }
+ while (!list_empty(&ksb->to_mdm_list)) {
+ pkt = list_first_entry(&ksb->to_mdm_list,
+ struct data_pkt, list);
+ list_del_init(&pkt->list);
+ ksb_free_data_pkt(pkt);
+ }
+ spin_unlock_irqrestore(&ksb->lock, flags);
+
+ ret = alloc_chrdev_region(&ksb->cdev_start_no, 0, 1, mdev->name);
+ if (ret < 0) {
+ dbg_log_event(ksb, "chr reg failed", ret, 0);
+ goto fail_chrdev_region;
+ }
+
+ ksb->class = class_create(THIS_MODULE, mdev->name);
+ if (IS_ERR(ksb->class)) {
+ dbg_log_event(ksb, "clscr failed", PTR_ERR(ksb->class), 0);
+ goto fail_class_create;
+ }
+
+ cdev_init(&ksb->cdev, &ksb_fops);
+ ksb->cdev.owner = THIS_MODULE;
+
+ ret = cdev_add(&ksb->cdev, ksb->cdev_start_no, 1);
+ if (ret < 0) {
+ dbg_log_event(ksb, "cdev_add failed", ret, 0);
+ goto fail_class_create;
+ }
+
+ ksb->device = device_create(ksb->class, &udev->dev, ksb->cdev_start_no,
+ NULL, mdev->name);
+ if (IS_ERR(ksb->device)) {
+ dbg_log_event(ksb, "devcrfailed", PTR_ERR(ksb->device), 0);
+ goto fail_device_create;
+ }
+
+ if (device_can_wakeup(&ksb->udev->dev))
+ ifc->needs_remote_wakeup = 1;
+
+ if (free_mdev)
+ kfree(mdev);
+ dev_dbg(&udev->dev, "usb dev connected");
+
+ return 0;
+
+fail_device_create:
+ cdev_del(&ksb->cdev);
+fail_class_create:
+ unregister_chrdev_region(ksb->cdev_start_no, 1);
+fail_chrdev_region:
+ usb_set_intfdata(ifc, NULL);
+ clear_bit(USB_DEV_CONNECTED, &ksb->flags);
+
+ if (free_mdev)
+ kfree(mdev);
+
+ return -ENODEV;
+
+}
+
+static int ksb_usb_suspend(struct usb_interface *ifc, pm_message_t message)
+{
+ struct ks_bridge *ksb = usb_get_intfdata(ifc);
+ unsigned long flags;
+
+ dbg_log_event(ksb, "SUSPEND", 0, 0);
+
+ if (pm_runtime_autosuspend_expiration(&ksb->udev->dev)) {
+ dbg_log_event(ksb, "SUSP ABORT-TimeCheck", 0, 0);
+ return -EBUSY;
+ }
+
+ usb_kill_anchored_urbs(&ksb->submitted);
+
+ spin_lock_irqsave(&ksb->lock, flags);
+ if (!list_empty(&ksb->to_ks_list)) {
+ spin_unlock_irqrestore(&ksb->lock, flags);
+ dbg_log_event(ksb, "SUSPEND ABORT", 0, 0);
+ /*
+ * Now wakeup the reader process and queue
+ * Rx URBs for more data.
+ */
+ wake_up(&ksb->ks_wait_q);
+ queue_work(ksb->wq, &ksb->start_rx_work);
+ return -EBUSY;
+ }
+ spin_unlock_irqrestore(&ksb->lock, flags);
+
+ return 0;
+}
+
+static int ksb_usb_resume(struct usb_interface *ifc)
+{
+ struct ks_bridge *ksb = usb_get_intfdata(ifc);
+
+ dbg_log_event(ksb, "RESUME", 0, 0);
+
+ if (test_bit(FILE_OPENED, &ksb->flags))
+ queue_work(ksb->wq, &ksb->start_rx_work);
+
+ return 0;
+}
+
+static void ksb_usb_disconnect(struct usb_interface *ifc)
+{
+ struct ks_bridge *ksb = usb_get_intfdata(ifc);
+ unsigned long flags;
+ struct data_pkt *pkt;
+
+ dbg_log_event(ksb, "PID-DETACH", 0, 0);
+
+ clear_bit(USB_DEV_CONNECTED, &ksb->flags);
+ wake_up(&ksb->ks_wait_q);
+ cancel_work_sync(&ksb->to_mdm_work);
+ cancel_work_sync(&ksb->start_rx_work);
+
+ device_destroy(ksb->class, ksb->cdev_start_no);
+ cdev_del(&ksb->cdev);
+ class_destroy(ksb->class);
+ unregister_chrdev_region(ksb->cdev_start_no, 1);
+
+ usb_kill_anchored_urbs(&ksb->submitted);
+
+ wait_event_interruptible_timeout(
+ ksb->pending_urb_wait,
+ !atomic_read(&ksb->tx_pending_cnt) &&
+ !atomic_read(&ksb->rx_pending_cnt),
+ msecs_to_jiffies(PENDING_URB_TIMEOUT));
+
+ spin_lock_irqsave(&ksb->lock, flags);
+ while (!list_empty(&ksb->to_ks_list)) {
+ pkt = list_first_entry(&ksb->to_ks_list,
+ struct data_pkt, list);
+ list_del_init(&pkt->list);
+ ksb_free_data_pkt(pkt);
+ }
+ while (!list_empty(&ksb->to_mdm_list)) {
+ pkt = list_first_entry(&ksb->to_mdm_list,
+ struct data_pkt, list);
+ list_del_init(&pkt->list);
+ ksb_free_data_pkt(pkt);
+ }
+ spin_unlock_irqrestore(&ksb->lock, flags);
+
+ ifc->needs_remote_wakeup = 0;
+ usb_put_dev(ksb->udev);
+ ksb->ifc = NULL;
+ usb_set_intfdata(ifc, NULL);
+}
+
+static struct usb_driver ksb_usb_driver = {
+ .name = "ks_bridge",
+ .probe = ksb_usb_probe,
+ .disconnect = ksb_usb_disconnect,
+ .suspend = ksb_usb_suspend,
+ .resume = ksb_usb_resume,
+ .reset_resume = ksb_usb_resume,
+ .id_table = ksb_usb_ids,
+ .supports_autosuspend = 1,
+};
+
+static int ksb_debug_show(struct seq_file *s, void *unused)
+{
+ unsigned long flags;
+ struct ks_bridge *ksb = s->private;
+ int i;
+
+ read_lock_irqsave(&ksb->dbg_lock, flags);
+ for (i = 0; i < DBG_MAX_MSG; i++) {
+ if (i == (ksb->dbg_idx - 1))
+ seq_printf(s, "-->%s\n", ksb->dbgbuf[i]);
+ else
+ seq_printf(s, "%s\n", ksb->dbgbuf[i]);
+ }
+ read_unlock_irqrestore(&ksb->dbg_lock, flags);
+
+ return 0;
+}
+
+static int ksb_debug_open(struct inode *ip, struct file *fp)
+{
+ return single_open(fp, ksb_debug_show, ip->i_private);
+
+ return 0;
+}
+
+static const struct file_operations dbg_fops = {
+ .open = ksb_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static struct dentry *dbg_dir;
+
+static int __init ksb_init(void)
+{
+ struct ks_bridge *ksb;
+ int num_instances = 0;
+ int ret = 0;
+ int i;
+
+ dbg_dir = debugfs_create_dir("ks_bridge", NULL);
+ if (IS_ERR(dbg_dir))
+ pr_err("unable to create debug dir");
+
+ for (i = 0; i < NO_BRIDGE_INSTANCES; i++) {
+ ksb = kzalloc(sizeof(struct ks_bridge), GFP_KERNEL);
+ if (!ksb) {
+ pr_err("unable to allocat mem for ks_bridge");
+ ret = -ENOMEM;
+ goto dev_free;
+ }
+ __ksb[i] = ksb;
+
+ ksb->name = kasprintf(GFP_KERNEL, "ks_usb_bridge.%i", i);
+ if (!ksb->name) {
+ pr_info("unable to allocate name");
+ kfree(ksb);
+ ret = -ENOMEM;
+ goto dev_free;
+ }
+
+ spin_lock_init(&ksb->lock);
+ INIT_LIST_HEAD(&ksb->to_mdm_list);
+ INIT_LIST_HEAD(&ksb->to_ks_list);
+ init_waitqueue_head(&ksb->ks_wait_q);
+ init_waitqueue_head(&ksb->pending_urb_wait);
+ ksb->wq = create_singlethread_workqueue(ksb->name);
+ if (!ksb->wq) {
+ pr_err("unable to allocate workqueue");
+ kfree(ksb->name);
+ kfree(ksb);
+ ret = -ENOMEM;
+ goto dev_free;
+ }
+
+ INIT_WORK(&ksb->to_mdm_work, ksb_tomdm_work);
+ INIT_WORK(&ksb->start_rx_work, ksb_start_rx_work);
+ init_usb_anchor(&ksb->submitted);
+
+ ksb->dbg_idx = 0;
+ ksb->dbg_lock = __RW_LOCK_UNLOCKED(lck);
+
+ if (!IS_ERR(dbg_dir))
+ debugfs_create_file(ksb->name, S_IRUGO, dbg_dir,
+ ksb, &dbg_fops);
+
+ num_instances++;
+ }
+
+ ret = usb_register(&ksb_usb_driver);
+ if (ret) {
+ pr_err("unable to register ks bridge driver");
+ goto dev_free;
+ }
+
+ pr_info("init done");
+
+ return 0;
+
+dev_free:
+ if (!IS_ERR(dbg_dir))
+ debugfs_remove_recursive(dbg_dir);
+
+ for (i = 0; i < num_instances; i++) {
+ ksb = __ksb[i];
+
+ destroy_workqueue(ksb->wq);
+ kfree(ksb->name);
+ kfree(ksb);
+ }
+
+ return ret;
+
+}
+
+static void __exit ksb_exit(void)
+{
+ struct ks_bridge *ksb;
+ int i;
+
+ if (!IS_ERR(dbg_dir))
+ debugfs_remove_recursive(dbg_dir);
+
+ usb_deregister(&ksb_usb_driver);
+
+ for (i = 0; i < NO_BRIDGE_INSTANCES; i++) {
+ ksb = __ksb[i];
+
+ destroy_workqueue(ksb->wq);
+ kfree(ksb->name);
+ kfree(ksb);
+ }
+}
+
+module_init(ksb_init);
+module_exit(ksb_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile
index f6f65ccce8e7..238e851c0705 100644
--- a/drivers/video/fbdev/Makefile
+++ b/drivers/video/fbdev/Makefile
@@ -126,7 +126,11 @@ obj-y += omap2/
obj-$(CONFIG_XEN_FBDEV_FRONTEND) += xen-fbfront.o
obj-$(CONFIG_FB_CARMINE) += carminefb.o
obj-$(CONFIG_FB_MB862XX) += mb862xx/
-obj-$(CONFIG_FB_MSM) += msm/
+ifeq ($(CONFIG_FB_MSM),y)
+obj-y += msm/
+else
+obj-$(CONFIG_MSM_DBA) += msm/msm_dba/
+endif
obj-$(CONFIG_FB_NUC900) += nuc900fb.o
obj-$(CONFIG_FB_JZ4740) += jz4740_fb.o
obj-$(CONFIG_FB_PUV3_UNIGFX) += fb-puv3.o
diff --git a/drivers/video/fbdev/msm/msm_dba/adv7533.c b/drivers/video/fbdev/msm/msm_dba/adv7533.c
index 8503d84e0de4..63e178d76403 100644
--- a/drivers/video/fbdev/msm/msm_dba/adv7533.c
+++ b/drivers/video/fbdev/msm/msm_dba/adv7533.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2015-2017, 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
@@ -31,7 +31,7 @@
#define ADV7533_REG_CHIP_REVISION (0x00)
#define ADV7533_DSI_CEC_I2C_ADDR_REG (0xE1)
-#define ADV7533_RESET_DELAY (100)
+#define ADV7533_RESET_DELAY (10)
#define PINCTRL_STATE_ACTIVE "pmx_adv7533_active"
#define PINCTRL_STATE_SUSPEND "pmx_adv7533_suspend"
@@ -1539,14 +1539,14 @@ exit:
static int adv7533_video_on(void *client, bool on,
struct msm_dba_video_cfg *cfg, u32 flags)
{
- int ret = -EINVAL;
+ int ret = 0;
u8 lanes;
u8 reg_val = 0;
struct adv7533 *pdata = adv7533_get_platform_data(client);
if (!pdata || !cfg) {
pr_err("%s: invalid platform data\n", __func__);
- return ret;
+ return -EINVAL;
}
mutex_lock(&pdata->ops_mutex);
diff --git a/drivers/video/fbdev/msm/msm_dba/msm_dba_helpers.c b/drivers/video/fbdev/msm/msm_dba/msm_dba_helpers.c
index f6128ae01a75..a0b45bfccb3c 100644
--- a/drivers/video/fbdev/msm/msm_dba/msm_dba_helpers.c
+++ b/drivers/video/fbdev/msm/msm_dba/msm_dba_helpers.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2015, 2017, 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
@@ -123,7 +123,7 @@ int msm_dba_helper_i2c_write_byte(struct i2c_client *client,
return -EINVAL;
}
- pr_debug("%s: [%s:0x02%x] : W[0x%02x, 0x%02x]\n", __func__,
+ pr_debug("%s: [%s:0x%02x] : W[0x%02x, 0x%02x]\n", __func__,
client->name, addr, reg, val);
client->addr = addr;