| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. |
| */ |
| |
| #include <linux/of_gpio.h> |
| #include <linux/phy/phy.h> |
| |
| #include <drm/drm_of.h> |
| #include <drm/drm_print.h> |
| #include <drm/drm_bridge.h> |
| |
| #include "dp_parser.h" |
| #include "dp_reg.h" |
| |
| #define DP_DEFAULT_AHB_OFFSET 0x0000 |
| #define DP_DEFAULT_AHB_SIZE 0x0200 |
| #define DP_DEFAULT_AUX_OFFSET 0x0200 |
| #define DP_DEFAULT_AUX_SIZE 0x0200 |
| #define DP_DEFAULT_LINK_OFFSET 0x0400 |
| #define DP_DEFAULT_LINK_SIZE 0x0C00 |
| #define DP_DEFAULT_P0_OFFSET 0x1000 |
| #define DP_DEFAULT_P0_SIZE 0x0400 |
| |
| static const struct dp_regulator_cfg sdm845_dp_reg_cfg = { |
| .num = 2, |
| .regs = { |
| {"vdda-1p2", 21800, 4 }, /* 1.2 V */ |
| {"vdda-0p9", 36000, 32 }, /* 0.9 V */ |
| }, |
| }; |
| |
| static void __iomem *dp_ioremap(struct platform_device *pdev, int idx, size_t *len) |
| { |
| struct resource *res; |
| void __iomem *base; |
| |
| base = devm_platform_get_and_ioremap_resource(pdev, idx, &res); |
| if (!IS_ERR(base)) |
| *len = resource_size(res); |
| |
| return base; |
| } |
| |
| static int dp_parser_ctrl_res(struct dp_parser *parser) |
| { |
| struct platform_device *pdev = parser->pdev; |
| struct dp_io *io = &parser->io; |
| struct dss_io_data *dss = &io->dp_controller; |
| |
| dss->ahb.base = dp_ioremap(pdev, 0, &dss->ahb.len); |
| if (IS_ERR(dss->ahb.base)) |
| return PTR_ERR(dss->ahb.base); |
| |
| dss->aux.base = dp_ioremap(pdev, 1, &dss->aux.len); |
| if (IS_ERR(dss->aux.base)) { |
| /* |
| * The initial binding had a single reg, but in order to |
| * support variation in the sub-region sizes this was split. |
| * dp_ioremap() will fail with -EINVAL here if only a single |
| * reg is specified, so fill in the sub-region offsets and |
| * lengths based on this single region. |
| */ |
| if (PTR_ERR(dss->aux.base) == -EINVAL) { |
| if (dss->ahb.len < DP_DEFAULT_P0_OFFSET + DP_DEFAULT_P0_SIZE) { |
| DRM_ERROR("legacy memory region not large enough\n"); |
| return -EINVAL; |
| } |
| |
| dss->ahb.len = DP_DEFAULT_AHB_SIZE; |
| dss->aux.base = dss->ahb.base + DP_DEFAULT_AUX_OFFSET; |
| dss->aux.len = DP_DEFAULT_AUX_SIZE; |
| dss->link.base = dss->ahb.base + DP_DEFAULT_LINK_OFFSET; |
| dss->link.len = DP_DEFAULT_LINK_SIZE; |
| dss->p0.base = dss->ahb.base + DP_DEFAULT_P0_OFFSET; |
| dss->p0.len = DP_DEFAULT_P0_SIZE; |
| } else { |
| DRM_ERROR("unable to remap aux region: %pe\n", dss->aux.base); |
| return PTR_ERR(dss->aux.base); |
| } |
| } else { |
| dss->link.base = dp_ioremap(pdev, 2, &dss->link.len); |
| if (IS_ERR(dss->link.base)) { |
| DRM_ERROR("unable to remap link region: %pe\n", dss->link.base); |
| return PTR_ERR(dss->link.base); |
| } |
| |
| dss->p0.base = dp_ioremap(pdev, 3, &dss->p0.len); |
| if (IS_ERR(dss->p0.base)) { |
| DRM_ERROR("unable to remap p0 region: %pe\n", dss->p0.base); |
| return PTR_ERR(dss->p0.base); |
| } |
| } |
| |
| io->phy = devm_phy_get(&pdev->dev, "dp"); |
| if (IS_ERR(io->phy)) |
| return PTR_ERR(io->phy); |
| |
| return 0; |
| } |
| |
| static int dp_parser_misc(struct dp_parser *parser) |
| { |
| struct device_node *of_node = parser->pdev->dev.of_node; |
| int len = 0; |
| const char *data_lane_property = "data-lanes"; |
| |
| len = of_property_count_elems_of_size(of_node, |
| data_lane_property, sizeof(u32)); |
| if (len < 0) { |
| DRM_WARN("Invalid property %s, default max DP lanes = %d\n", |
| data_lane_property, DP_MAX_NUM_DP_LANES); |
| len = DP_MAX_NUM_DP_LANES; |
| } |
| |
| parser->max_dp_lanes = len; |
| return 0; |
| } |
| |
| static inline bool dp_parser_check_prefix(const char *clk_prefix, |
| const char *clk_name) |
| { |
| return !strncmp(clk_prefix, clk_name, strlen(clk_prefix)); |
| } |
| |
| static int dp_parser_init_clk_data(struct dp_parser *parser) |
| { |
| int num_clk, i, rc; |
| int core_clk_count = 0, ctrl_clk_count = 0, stream_clk_count = 0; |
| const char *clk_name; |
| struct device *dev = &parser->pdev->dev; |
| struct dss_module_power *core_power = &parser->mp[DP_CORE_PM]; |
| struct dss_module_power *ctrl_power = &parser->mp[DP_CTRL_PM]; |
| struct dss_module_power *stream_power = &parser->mp[DP_STREAM_PM]; |
| |
| num_clk = of_property_count_strings(dev->of_node, "clock-names"); |
| if (num_clk <= 0) { |
| DRM_ERROR("no clocks are defined\n"); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < num_clk; i++) { |
| rc = of_property_read_string_index(dev->of_node, |
| "clock-names", i, &clk_name); |
| if (rc < 0) |
| return rc; |
| |
| if (dp_parser_check_prefix("core", clk_name)) |
| core_clk_count++; |
| |
| if (dp_parser_check_prefix("ctrl", clk_name)) |
| ctrl_clk_count++; |
| |
| if (dp_parser_check_prefix("stream", clk_name)) |
| stream_clk_count++; |
| } |
| |
| /* Initialize the CORE power module */ |
| if (core_clk_count == 0) { |
| DRM_ERROR("no core clocks are defined\n"); |
| return -EINVAL; |
| } |
| |
| core_power->num_clk = core_clk_count; |
| core_power->clk_config = devm_kzalloc(dev, |
| sizeof(struct dss_clk) * core_power->num_clk, |
| GFP_KERNEL); |
| if (!core_power->clk_config) |
| return -EINVAL; |
| |
| /* Initialize the CTRL power module */ |
| if (ctrl_clk_count == 0) { |
| DRM_ERROR("no ctrl clocks are defined\n"); |
| return -EINVAL; |
| } |
| |
| ctrl_power->num_clk = ctrl_clk_count; |
| ctrl_power->clk_config = devm_kzalloc(dev, |
| sizeof(struct dss_clk) * ctrl_power->num_clk, |
| GFP_KERNEL); |
| if (!ctrl_power->clk_config) { |
| ctrl_power->num_clk = 0; |
| return -EINVAL; |
| } |
| |
| /* Initialize the STREAM power module */ |
| if (stream_clk_count == 0) { |
| DRM_ERROR("no stream (pixel) clocks are defined\n"); |
| return -EINVAL; |
| } |
| |
| stream_power->num_clk = stream_clk_count; |
| stream_power->clk_config = devm_kzalloc(dev, |
| sizeof(struct dss_clk) * stream_power->num_clk, |
| GFP_KERNEL); |
| if (!stream_power->clk_config) { |
| stream_power->num_clk = 0; |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int dp_parser_clock(struct dp_parser *parser) |
| { |
| int rc = 0, i = 0; |
| int num_clk = 0; |
| int core_clk_index = 0, ctrl_clk_index = 0, stream_clk_index = 0; |
| int core_clk_count = 0, ctrl_clk_count = 0, stream_clk_count = 0; |
| const char *clk_name; |
| struct device *dev = &parser->pdev->dev; |
| struct dss_module_power *core_power = &parser->mp[DP_CORE_PM]; |
| struct dss_module_power *ctrl_power = &parser->mp[DP_CTRL_PM]; |
| struct dss_module_power *stream_power = &parser->mp[DP_STREAM_PM]; |
| |
| rc = dp_parser_init_clk_data(parser); |
| if (rc) { |
| DRM_ERROR("failed to initialize power data %d\n", rc); |
| return -EINVAL; |
| } |
| |
| core_clk_count = core_power->num_clk; |
| ctrl_clk_count = ctrl_power->num_clk; |
| stream_clk_count = stream_power->num_clk; |
| |
| num_clk = core_clk_count + ctrl_clk_count + stream_clk_count; |
| |
| for (i = 0; i < num_clk; i++) { |
| rc = of_property_read_string_index(dev->of_node, "clock-names", |
| i, &clk_name); |
| if (rc) { |
| DRM_ERROR("error reading clock-names %d\n", rc); |
| return rc; |
| } |
| if (dp_parser_check_prefix("core", clk_name) && |
| core_clk_index < core_clk_count) { |
| struct dss_clk *clk = |
| &core_power->clk_config[core_clk_index]; |
| strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); |
| clk->type = DSS_CLK_AHB; |
| core_clk_index++; |
| } else if (dp_parser_check_prefix("stream", clk_name) && |
| stream_clk_index < stream_clk_count) { |
| struct dss_clk *clk = |
| &stream_power->clk_config[stream_clk_index]; |
| strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); |
| clk->type = DSS_CLK_PCLK; |
| stream_clk_index++; |
| } else if (dp_parser_check_prefix("ctrl", clk_name) && |
| ctrl_clk_index < ctrl_clk_count) { |
| struct dss_clk *clk = |
| &ctrl_power->clk_config[ctrl_clk_index]; |
| strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); |
| ctrl_clk_index++; |
| if (dp_parser_check_prefix("ctrl_link", clk_name) || |
| dp_parser_check_prefix("stream_pixel", clk_name)) |
| clk->type = DSS_CLK_PCLK; |
| else |
| clk->type = DSS_CLK_AHB; |
| } |
| } |
| |
| DRM_DEBUG_DP("clock parsing successful\n"); |
| |
| return 0; |
| } |
| |
| static int dp_parser_find_panel(struct dp_parser *parser) |
| { |
| struct device *dev = &parser->pdev->dev; |
| struct drm_panel *panel; |
| int rc; |
| |
| rc = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL); |
| if (rc) { |
| DRM_ERROR("failed to acquire DRM panel: %d\n", rc); |
| return rc; |
| } |
| |
| parser->panel_bridge = devm_drm_panel_bridge_add(dev, panel); |
| if (IS_ERR(parser->panel_bridge)) { |
| DRM_ERROR("failed to create panel bridge\n"); |
| return PTR_ERR(parser->panel_bridge); |
| } |
| |
| return 0; |
| } |
| |
| static int dp_parser_parse(struct dp_parser *parser, int connector_type) |
| { |
| int rc = 0; |
| |
| if (!parser) { |
| DRM_ERROR("invalid input\n"); |
| return -EINVAL; |
| } |
| |
| rc = dp_parser_ctrl_res(parser); |
| if (rc) |
| return rc; |
| |
| rc = dp_parser_misc(parser); |
| if (rc) |
| return rc; |
| |
| rc = dp_parser_clock(parser); |
| if (rc) |
| return rc; |
| |
| if (connector_type == DRM_MODE_CONNECTOR_eDP) { |
| rc = dp_parser_find_panel(parser); |
| if (rc) |
| return rc; |
| } |
| |
| /* Map the corresponding regulator information according to |
| * version. Currently, since we only have one supported platform, |
| * mapping the regulator directly. |
| */ |
| parser->regulator_cfg = &sdm845_dp_reg_cfg; |
| |
| return 0; |
| } |
| |
| struct dp_parser *dp_parser_get(struct platform_device *pdev) |
| { |
| struct dp_parser *parser; |
| |
| parser = devm_kzalloc(&pdev->dev, sizeof(*parser), GFP_KERNEL); |
| if (!parser) |
| return ERR_PTR(-ENOMEM); |
| |
| parser->parse = dp_parser_parse; |
| parser->pdev = pdev; |
| |
| return parser; |
| } |