/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #ifdef CNSS2_CPR #include #endif #include "main.h" #include "debug.h" #ifdef CNSS2_VREG static struct cnss_vreg_cfg cnss_vreg_list[] = { {"vdd-wlan-core", 1300000, 1300000, 0, 0, 0}, {"vdd-wlan-io", 1800000, 1800000, 0, 0, 0}, {"vdd-wlan-xtal-aon", 0, 0, 0, 0, 0}, {"vdd-wlan-xtal", 1800000, 1800000, 0, 2, 0}, {"vdd-wlan", 0, 0, 0, 0, 0}, {"vdd-wlan-ctrl1", 0, 0, 0, 0, 0}, {"vdd-wlan-ctrl2", 0, 0, 0, 0, 0}, {"vdd-wlan-sp2t", 2700000, 2700000, 0, 0, 0}, {"wlan-ant-switch", 1800000, 1800000, 0, 0, 0}, {"wlan-soc-swreg", 1200000, 1200000, 0, 0, 0}, {"vdd-wlan-aon", 950000, 950000, 0, 0, 0}, {"vdd-wlan-dig", 950000, 952000, 0, 0, 0}, {"vdd-wlan-rfa1", 1900000, 1900000, 0, 0, 0}, {"vdd-wlan-rfa2", 1350000, 1350000, 0, 0, 0}, {"vdd-wlan-en", 0, 0, 0, 10, 0}, }; static struct cnss_clk_cfg cnss_clk_list[] = { {"rf_clk", 0, 0}, }; #define CNSS_VREG_INFO_SIZE ARRAY_SIZE(cnss_vreg_list) #define CNSS_CLK_INFO_SIZE ARRAY_SIZE(cnss_clk_list) #define MAX_PROP_SIZE 32 #endif #define BOOTSTRAP_GPIO "qcom,enable-bootstrap-gpio" #define BOOTSTRAP_ACTIVE "bootstrap_active" #define WLAN_EN_GPIO "wlan-en-gpio" #define WLAN_EN_ACTIVE "wlan_en_active" #define WLAN_EN_SLEEP "wlan_en_sleep" #define BOOTSTRAP_DELAY 1000 #define WLAN_ENABLE_DELAY 1000 #define TCS_CMD_DATA_ADDR_OFFSET 0x4 #define TCS_OFFSET 0xC8 #define TCS_CMD_OFFSET 0x10 #define MAX_TCS_NUM 8 #define MAX_TCS_CMD_NUM 5 #define BT_CXMX_VOLTAGE_MV 950 #ifdef CNSS2_VREG static int cnss_get_vreg_single(struct cnss_plat_data *plat_priv, struct cnss_vreg_info *vreg) { int ret = 0; struct device *dev; struct regulator *reg; const __be32 *prop; char prop_name[MAX_PROP_SIZE] = {0}; int len; dev = &plat_priv->plat_dev->dev; reg = devm_regulator_get_optional(dev, vreg->cfg.name); if (IS_ERR(reg)) { ret = PTR_ERR(reg); if (ret == -ENODEV) return ret; else if (ret == -EPROBE_DEFER) cnss_pr_info("EPROBE_DEFER for regulator: %s\n", vreg->cfg.name); else cnss_pr_err("Failed to get regulator %s, err = %d\n", vreg->cfg.name, ret); return ret; } vreg->reg = reg; snprintf(prop_name, MAX_PROP_SIZE, "qcom,%s-config", vreg->cfg.name); prop = of_get_property(dev->of_node, prop_name, &len); if (!prop || len != (5 * sizeof(__be32))) { cnss_pr_dbg("Property %s %s, use default\n", prop_name, prop ? "invalid format" : "doesn't exist"); } else { vreg->cfg.min_uv = be32_to_cpup(&prop[0]); vreg->cfg.max_uv = be32_to_cpup(&prop[1]); vreg->cfg.load_ua = be32_to_cpup(&prop[2]); vreg->cfg.delay_us = be32_to_cpup(&prop[3]); vreg->cfg.need_unvote = be32_to_cpup(&prop[4]); } cnss_pr_dbg("Got regulator: %s, min_uv: %u, max_uv: %u, load_ua: %u, delay_us: %u, need_unvote: %u\n", vreg->cfg.name, vreg->cfg.min_uv, vreg->cfg.max_uv, vreg->cfg.load_ua, vreg->cfg.delay_us, vreg->cfg.need_unvote); return 0; } static void cnss_put_vreg_single(struct cnss_plat_data *plat_priv, struct cnss_vreg_info *vreg) { struct device *dev = &plat_priv->plat_dev->dev; cnss_pr_dbg("Put regulator: %s\n", vreg->cfg.name); devm_regulator_put(vreg->reg); devm_kfree(dev, vreg); } static int cnss_vreg_on_single(struct cnss_vreg_info *vreg) { int ret = 0; if (vreg->enabled) { cnss_pr_dbg("Regulator %s is already enabled\n", vreg->cfg.name); return 0; } cnss_pr_dbg("Regulator %s is being enabled\n", vreg->cfg.name); if (vreg->cfg.min_uv != 0 && vreg->cfg.max_uv != 0) { ret = regulator_set_voltage(vreg->reg, vreg->cfg.min_uv, vreg->cfg.max_uv); if (ret) { cnss_pr_err("Failed to set voltage for regulator %s, min_uv: %u, max_uv: %u, err = %d\n", vreg->cfg.name, vreg->cfg.min_uv, vreg->cfg.max_uv, ret); goto out; } } if (vreg->cfg.load_ua) { ret = regulator_set_load(vreg->reg, vreg->cfg.load_ua); if (ret < 0) { cnss_pr_err("Failed to set load for regulator %s, load: %u, err = %d\n", vreg->cfg.name, vreg->cfg.load_ua, ret); goto out; } } if (vreg->cfg.delay_us) udelay(vreg->cfg.delay_us); ret = regulator_enable(vreg->reg); if (ret) { cnss_pr_err("Failed to enable regulator %s, err = %d\n", vreg->cfg.name, ret); goto out; } vreg->enabled = true; out: return ret; } static int cnss_vreg_unvote_single(struct cnss_vreg_info *vreg) { int ret = 0; if (!vreg->enabled) { cnss_pr_dbg("Regulator %s is already disabled\n", vreg->cfg.name); return 0; } cnss_pr_dbg("Removing vote for Regulator %s\n", vreg->cfg.name); if (vreg->cfg.load_ua) { ret = regulator_set_load(vreg->reg, 0); if (ret < 0) cnss_pr_err("Failed to set load for regulator %s, err = %d\n", vreg->cfg.name, ret); } if (vreg->cfg.min_uv != 0 && vreg->cfg.max_uv != 0) { ret = regulator_set_voltage(vreg->reg, 0, vreg->cfg.max_uv); if (ret) cnss_pr_err("Failed to set voltage for regulator %s, err = %d\n", vreg->cfg.name, ret); } return ret; } static int cnss_vreg_off_single(struct cnss_vreg_info *vreg) { int ret = 0; if (!vreg->enabled) { cnss_pr_dbg("Regulator %s is already disabled\n", vreg->cfg.name); return 0; } cnss_pr_dbg("Regulator %s is being disabled\n", vreg->cfg.name); ret = regulator_disable(vreg->reg); if (ret) cnss_pr_err("Failed to disable regulator %s, err = %d\n", vreg->cfg.name, ret); if (vreg->cfg.load_ua) { ret = regulator_set_load(vreg->reg, 0); if (ret < 0) cnss_pr_err("Failed to set load for regulator %s, err = %d\n", vreg->cfg.name, ret); } if (vreg->cfg.min_uv != 0 && vreg->cfg.max_uv != 0) { ret = regulator_set_voltage(vreg->reg, 0, vreg->cfg.max_uv); if (ret) cnss_pr_err("Failed to set voltage for regulator %s, err = %d\n", vreg->cfg.name, ret); } vreg->enabled = false; return ret; } static struct cnss_vreg_cfg *get_vreg_list(u32 *vreg_list_size, enum cnss_vreg_type type) { switch (type) { case CNSS_VREG_PRIM: *vreg_list_size = CNSS_VREG_INFO_SIZE; return cnss_vreg_list; default: cnss_pr_err("Unsupported vreg type 0x%x\n", type); *vreg_list_size = 0; return NULL; } } int cnss_get_vreg(struct cnss_plat_data *plat_priv) { int ret = 0; int i; struct cnss_vreg_info *vreg_info; struct device *dev; struct regulator *reg; const __be32 *prop; char prop_name[MAX_PROP_SIZE]; int len; dev = &plat_priv->plat_dev->dev; plat_priv->vreg_info = devm_kzalloc(dev, sizeof(cnss_vreg_info), GFP_KERNEL); if (!plat_priv->vreg_info) { ret = -ENOMEM; goto out; } memcpy(plat_priv->vreg_info, cnss_vreg_info, sizeof(cnss_vreg_info)); for (i = 0; i < CNSS_VREG_INFO_SIZE; i++) { vreg_info = &plat_priv->vreg_info[i]; reg = devm_regulator_get_optional(dev, vreg_info->name); if (IS_ERR(reg)) { ret = PTR_ERR(reg); if (ret == -ENODEV) continue; else if (ret == -EPROBE_DEFER) cnss_pr_info("EPROBE_DEFER for regulator: %s\n", vreg_info->name); else cnss_pr_err("Failed to get regulator %s, err = %d\n", vreg_info->name, ret); goto out; } vreg_info->reg = reg; snprintf(prop_name, MAX_PROP_SIZE, "qcom,%s-info", vreg_info->name); prop = of_get_property(dev->of_node, prop_name, &len); cnss_pr_dbg("Got regulator info, name: %s, len: %d\n", prop_name, len); if (!prop || len != (4 * sizeof(__be32))) { cnss_pr_dbg("Property %s %s, use default\n", prop_name, prop ? "invalid format" : "doesn't exist"); } else { vreg_info->min_uv = be32_to_cpup(&prop[0]); vreg_info->max_uv = be32_to_cpup(&prop[1]); vreg_info->load_ua = be32_to_cpup(&prop[2]); vreg_info->delay_us = be32_to_cpup(&prop[3]); } cnss_pr_dbg("Got regulator: %s, min_uv: %u, max_uv: %u, load_ua: %u, delay_us: %u\n", vreg_info->name, vreg_info->min_uv, vreg_info->max_uv, vreg_info->load_ua, vreg_info->delay_us); } return 0; out: return ret; } #endif #ifdef CONFIG_CNSS2_PM static int cnss_vreg_on(struct cnss_plat_data *plat_priv) { int ret = 0; struct cnss_vreg_info *vreg_info; int i; if (!plat_priv) { printk(KERN_ERR "plat_priv is NULL!\n"); return -ENODEV; } for (i = 0; i < CNSS_VREG_INFO_SIZE; i++) { vreg_info = &plat_priv->vreg_info[i]; if (!vreg_info->reg) continue; cnss_pr_dbg("Regulator %s is being enabled\n", vreg_info->name); if (vreg_info->min_uv != 0 && vreg_info->max_uv != 0) { ret = regulator_set_voltage(vreg_info->reg, vreg_info->min_uv, vreg_info->max_uv); if (ret) { cnss_pr_err("Failed to set voltage for regulator %s, min_uv: %u, max_uv: %u, err = %d\n", vreg_info->name, vreg_info->min_uv, vreg_info->max_uv, ret); break; } } if (vreg_info->load_ua) { ret = regulator_set_load(vreg_info->reg, vreg_info->load_ua); if (ret < 0) { cnss_pr_err("Failed to set load for regulator %s, load: %u, err = %d\n", vreg_info->name, vreg_info->load_ua, ret); break; } } if (vreg_info->delay_us) udelay(vreg_info->delay_us); ret = regulator_enable(vreg_info->reg); if (ret) { cnss_pr_err("Failed to enable regulator %s, err = %d\n", vreg_info->name, ret); break; } } if (ret) { for (; i >= 0; i--) { vreg_info = &plat_priv->vreg_info[i]; if (!vreg_info->reg) continue; regulator_disable(vreg_info->reg); if (vreg_info->load_ua) regulator_set_load(vreg_info->reg, 0); if (vreg_info->min_uv != 0 && vreg_info->max_uv != 0) regulator_set_voltage(vreg_info->reg, 0, vreg_info->max_uv); } return ret; } return 0; } static int cnss_vreg_off(struct cnss_plat_data *plat_priv) { int ret = 0; struct cnss_vreg_info *vreg_info; int i; if (!plat_priv) { pr_err("plat_priv is NULL!\n"); return -ENODEV; } for (i = CNSS_VREG_INFO_SIZE - 1; i >= 0; i--) { vreg_info = &plat_priv->vreg_info[i]; if (!vreg_info->reg) continue; cnss_pr_dbg("Regulator %s is being disabled\n", vreg_info->name); ret = regulator_disable(vreg_info->reg); if (ret) cnss_pr_err("Failed to disable regulator %s, err = %d\n", vreg_info->name, ret); if (vreg_info->load_ua) { ret = regulator_set_load(vreg_info->reg, 0); if (ret < 0) cnss_pr_err("Failed to set load for regulator %s, err = %d\n", vreg_info->name, ret); } if (vreg_info->min_uv != 0 && vreg_info->max_uv != 0) { ret = regulator_set_voltage(vreg_info->reg, 0, vreg_info->max_uv); if (ret) cnss_pr_err("Failed to set voltage for regulator %s, err = %d\n", vreg_info->name, ret); } } return ret; } #endif int cnss_get_pinctrl(struct cnss_plat_data *plat_priv) { int ret = 0; struct device *dev; struct cnss_pinctrl_info *pinctrl_info; dev = &plat_priv->plat_dev->dev; pinctrl_info = &plat_priv->pinctrl_info; pinctrl_info->pinctrl = devm_pinctrl_get(dev); if (IS_ERR_OR_NULL(pinctrl_info->pinctrl)) { ret = PTR_ERR(pinctrl_info->pinctrl); cnss_pr_err("Failed to get pinctrl, err = %d\n", ret); goto out; } if (of_find_property(dev->of_node, WLAN_EN_GPIO, NULL)) { pinctrl_info->wlan_en_active = pinctrl_lookup_state(pinctrl_info->pinctrl, WLAN_EN_ACTIVE); if (IS_ERR_OR_NULL(pinctrl_info->wlan_en_active)) { ret = PTR_ERR(pinctrl_info->wlan_en_active); cnss_pr_err("Failed to get wlan_en active state, err = %d\n", ret); goto out; } pinctrl_info->wlan_en_sleep = pinctrl_lookup_state(pinctrl_info->pinctrl, WLAN_EN_SLEEP); if (IS_ERR_OR_NULL(pinctrl_info->wlan_en_sleep)) { ret = PTR_ERR(pinctrl_info->wlan_en_sleep); cnss_pr_err("Failed to get wlan_en sleep state, err = %d\n", ret); goto out; } } return 0; out: return ret; } #ifdef CONFIG_CNSS2_PM static int cnss_select_pinctrl_state(struct cnss_plat_data *plat_priv, bool state) { int ret = 0; struct cnss_pinctrl_info *pinctrl_info; if (!plat_priv) { printk(KERN_ERR "plat_priv is NULL!\n"); ret = -ENODEV; goto out; } pinctrl_info = &plat_priv->pinctrl_info; if (state) { if (!IS_ERR_OR_NULL(pinctrl_info->wlan_en_active)) { ret = pinctrl_select_state(pinctrl_info->pinctrl, pinctrl_info-> wlan_en_active); if (ret) { cnss_pr_err("Failed to select wlan_en active state, err = %d\n", ret); goto out; } udelay(WLAN_ENABLE_DELAY); } } else { if (!IS_ERR_OR_NULL(pinctrl_info->wlan_en_sleep)) { ret = pinctrl_select_state(pinctrl_info->pinctrl, pinctrl_info->wlan_en_sleep); if (ret) { cnss_pr_err("Failed to select wlan_en sleep state, err = %d\n", ret); goto out; } } } return 0; out: return ret; } #endif int cnss_power_on_device(struct cnss_plat_data *plat_priv, int device_id) { #ifdef CONFIG_CNSS2_PM int ret; if (!plat_priv) { plat_priv = cnss_get_plat_priv_by_device_id(device_id); if (!plat_priv) { pr_err("%s: plat_priv is NULL: device id 0x%x\n", __func__, device_id); return -ENODEV; } } ret = cnss_select_pinctrl_state(plat_priv, true); if (ret) { cnss_pr_err("Failed to select pinctrl state, err = %d\n", ret); } return ret; #else return 0; #endif } EXPORT_SYMBOL(cnss_power_on_device); int cnss_power_off_device(struct cnss_plat_data *plat_priv, int device_id) { #ifdef CONFIG_CNSS2_PM int ret; if (!plat_priv) { plat_priv = cnss_get_plat_priv_by_device_id(device_id); if (!plat_priv) { pr_err("%s: plat_priv is NULL: device id 0x%x\n", __func__, device_id); return -ENODEV; } } ret = cnss_select_pinctrl_state(plat_priv, false); if (ret) cnss_pr_err("Failed to select pinctrl state, err = %d\n", ret); return ret; #else return 0; #endif } EXPORT_SYMBOL(cnss_power_off_device); void cnss_set_pin_connect_status(struct cnss_plat_data *plat_priv) { unsigned long pin_status = 0; set_bit(CNSS_WLAN_EN, &pin_status); set_bit(CNSS_PCIE_TXN, &pin_status); set_bit(CNSS_PCIE_TXP, &pin_status); set_bit(CNSS_PCIE_RXN, &pin_status); set_bit(CNSS_PCIE_RXP, &pin_status); set_bit(CNSS_PCIE_REFCLKN, &pin_status); set_bit(CNSS_PCIE_REFCLKP, &pin_status); set_bit(CNSS_PCIE_RST, &pin_status); plat_priv->pin_result.host_pin_result = pin_status; } int cnss_get_cpr_info(struct cnss_plat_data *plat_priv) { #ifdef CNSS2_CPR struct platform_device *plat_dev = plat_priv->plat_dev; struct cnss_cpr_info *cpr_info = &plat_priv->cpr_info; struct resource *res; resource_size_t addr_len; void __iomem *tcs_cmd_base_addr; const char *cmd_db_name; u32 cpr_pmic_addr = 0; int ret = 0; res = platform_get_resource_byname(plat_dev, IORESOURCE_MEM, "tcs_cmd"); if (!res) { cnss_pr_dbg("TCS CMD address is not present for CPR\n"); goto out; } ret = of_property_read_string(plat_dev->dev.of_node, "qcom,cmd_db_name", &cmd_db_name); if (ret) { cnss_pr_dbg("CommandDB name is not present for CPR\n"); goto out; } ret = cmd_db_ready(); if (ret) { cnss_pr_err("CommandDB is not ready\n"); goto out; } cpr_pmic_addr = cmd_db_read_addr(cmd_db_name); if (cpr_pmic_addr > 0) { cpr_info->cpr_pmic_addr = cpr_pmic_addr; cnss_pr_dbg("Get CPR PMIC address 0x%x from %s\n", cpr_info->cpr_pmic_addr, cmd_db_name); } else { cnss_pr_err("CPR PMIC address is not available for %s\n", cmd_db_name); ret = -EINVAL; goto out; } cpr_info->tcs_cmd_base_addr = res->start; addr_len = resource_size(res); cnss_pr_dbg("TCS CMD base address is %pa with length %pa\n", &cpr_info->tcs_cmd_base_addr, &addr_len); tcs_cmd_base_addr = devm_ioremap_resource(&plat_dev->dev, res); if (IS_ERR(tcs_cmd_base_addr)) { ret = PTR_ERR(tcs_cmd_base_addr); cnss_pr_err("Failed to map TCS CMD address, err = %d\n", ret); goto out; } cpr_info->tcs_cmd_base_addr_io = tcs_cmd_base_addr; return 0; out: return ret; #endif return 0; } int cnss_update_cpr_info(struct cnss_plat_data *plat_priv) { #ifdef CNSS2_CPR struct cnss_cpr_info *cpr_info = &plat_priv->cpr_info; u32 pmic_addr, voltage = 0, voltage_tmp, offset; void __iomem *tcs_cmd_addr, *tcs_cmd_data_addr; int i, j; if (cpr_info->tcs_cmd_base_addr == 0) { cnss_pr_dbg("CPR is not enabled\n"); return 0; } if (cpr_info->voltage == 0 || cpr_info->cpr_pmic_addr == 0) { cnss_pr_err("Voltage %dmV or PMIC address 0x%x is not valid\n", cpr_info->voltage, cpr_info->cpr_pmic_addr); return -EINVAL; } if (cpr_info->tcs_cmd_data_addr_io) goto update_cpr; for (i = 0; i < MAX_TCS_NUM; i++) { for (j = 0; j < MAX_TCS_CMD_NUM; j++) { offset = i * TCS_OFFSET + j * TCS_CMD_OFFSET; tcs_cmd_addr = cpr_info->tcs_cmd_base_addr_io + offset; pmic_addr = readl_relaxed(tcs_cmd_addr); if (pmic_addr == cpr_info->cpr_pmic_addr) { tcs_cmd_data_addr = tcs_cmd_addr + TCS_CMD_DATA_ADDR_OFFSET; voltage_tmp = readl_relaxed(tcs_cmd_data_addr); cnss_pr_dbg("Got voltage %dmV from i: %d, j: %d\n", voltage_tmp, i, j); if (voltage_tmp > voltage) { voltage = voltage_tmp; cpr_info->tcs_cmd_data_addr = cpr_info->tcs_cmd_base_addr + offset + TCS_CMD_DATA_ADDR_OFFSET; cpr_info->tcs_cmd_data_addr_io = tcs_cmd_data_addr; } } } } if (!cpr_info->tcs_cmd_data_addr_io) { cnss_pr_err("Failed to find proper TCS CMD data address\n"); return -EINVAL; } update_cpr: cpr_info->voltage = cpr_info->voltage > BT_CXMX_VOLTAGE_MV ? cpr_info->voltage : BT_CXMX_VOLTAGE_MV; cnss_pr_dbg("Update TCS CMD data address %pa with voltage %dmV\n", &cpr_info->tcs_cmd_data_addr, cpr_info->voltage); writel_relaxed(cpr_info->voltage, cpr_info->tcs_cmd_data_addr_io); #endif return 0; }