diff options
Diffstat (limited to 'drivers')
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; |