Merge "icnss: Vote for hardware resources"

This commit is contained in:
Linux Build Service Account 2017-03-01 02:22:48 -08:00 committed by Gerrit - the friendly Code Review server
commit 2d20bbca35
3 changed files with 374 additions and 11 deletions

View file

@ -12,9 +12,17 @@ Required properties:
- reg-names: Names of the memory regions defined in reg entry
- interrupts: Copy engine interrupt table
- qcom,wlan-msa-memory: MSA memory size
- clocks: List of clock phandles
- clock-names: List of clock names corresponding to the "clocks" property
- iommus: SMMUs and corresponding Stream IDs needed by WLAN
- qcom,wlan-smmu-iova-address: I/O virtual address range as <start length>
format to be used for allocations associated between WLAN and SMMU
- <supply-name>-supply: phandle to the regulator device tree node
Required "supply-name" is "vdd-0.8-cx-mx".
- qcom,<supply>-config: Specifies voltage levels for supply. Should be
specified in pairs (min, max), units uV. There can
be optional load in uA and Regulator settle delay in
uS.
Optional properties:
- qcom,icnss-vadc: VADC handle for vph_pwr read APIs.
@ -27,6 +35,8 @@ Example:
compatible = "qcom,icnss";
reg = <0x0a000000 0x1000000>;
reg-names = "membase";
clocks = <&clock_gcc clk_aggre2_noc_clk>;
clock-names = "smmu_aggre2_noc_clk";
iommus = <&anoc2_smmu 0x1900>,
<&anoc2_smmu 0x1901>;
qcom,wlan-smmu-iova-address = <0 0x10000000>;
@ -45,4 +55,6 @@ Example:
<0 141 0 /* CE11 */ >;
qcom,wlan-msa-memory = <0x200000>;
qcom,smmu-s1-bypass;
vdd-0.8-cx-mx-supply = <&pm8998_l5>;
qcom,vdd-0.8-cx-mx-config = <800000 800000 2400 1000>;
};

View file

@ -3079,6 +3079,8 @@
<0xa0000000 0x10000000>,
<0xb0000000 0x10000>;
reg-names = "membase", "smmu_iova_base", "smmu_iova_ipa";
clocks = <&clock_gcc clk_rf_clk2_pin>;
clock-names = "cxo_ref_clk_pin";
iommus = <&anoc2_smmu 0x1900>,
<&anoc2_smmu 0x1901>;
interrupts = <0 413 0 /* CE0 */ >,
@ -3094,6 +3096,12 @@
<0 424 0 /* CE10 */ >,
<0 425 0 /* CE11 */ >;
qcom,wlan-msa-memory = <0x100000>;
vdd-0.8-cx-mx-supply = <&pm8998_l5>;
vdd-1.8-xo-supply = <&pm8998_l7_pin_ctrl>;
vdd-1.3-rfa-supply = <&pm8998_l17_pin_ctrl>;
vdd-3.3-ch0-supply = <&pm8998_l25_pin_ctrl>;
qcom,vdd-0.8-cx-mx-config = <800000 800000>;
qcom,vdd-3.3-ch0-config = <3104000 3312000>;
qcom,icnss-vadc = <&pm8998_vadc>;
qcom,icnss-adc_tm = <&pm8998_adc_tm>;
};

View file

@ -195,6 +195,38 @@ struct ce_irq_list {
irqreturn_t (*handler)(int, void *);
};
struct icnss_vreg_info {
struct regulator *reg;
const char *name;
u32 min_v;
u32 max_v;
u32 load_ua;
unsigned long settle_delay;
bool required;
};
struct icnss_clk_info {
struct clk *handle;
const char *name;
u32 freq;
bool required;
};
static struct icnss_vreg_info icnss_vreg_info[] = {
{NULL, "vdd-0.8-cx-mx", 800000, 800000, 0, 0, true},
{NULL, "vdd-1.8-xo", 1800000, 1800000, 0, 0, false},
{NULL, "vdd-1.3-rfa", 1304000, 1304000, 0, 0, false},
{NULL, "vdd-3.3-ch0", 3312000, 3312000, 0, 0, false},
};
#define ICNSS_VREG_INFO_SIZE ARRAY_SIZE(icnss_vreg_info)
static struct icnss_clk_info icnss_clk_info[] = {
{NULL, "cxo_ref_clk_pin", 0, false},
};
#define ICNSS_CLK_INFO_SIZE ARRAY_SIZE(icnss_clk_info)
struct icnss_stats {
struct {
uint32_t posted;
@ -248,6 +280,7 @@ struct icnss_stats {
uint32_t rejuvenate_ack_req;
uint32_t rejuvenate_ack_resp;
uint32_t rejuvenate_ack_err;
uint32_t trigger_recovery;
};
#define MAX_NO_OF_MAC_ADDR 4
@ -267,6 +300,8 @@ static struct icnss_priv {
struct platform_device *pdev;
struct icnss_driver_ops *ops;
struct ce_irq_list ce_irq_list[ICNSS_MAX_IRQ_REGISTRATIONS];
struct icnss_vreg_info vreg_info[ICNSS_VREG_INFO_SIZE];
struct icnss_clk_info clk_info[ICNSS_CLK_INFO_SIZE];
u32 ce_irqs[ICNSS_MAX_IRQ_REGISTRATIONS];
phys_addr_t mem_base_pa;
void __iomem *mem_base_va;
@ -664,41 +699,220 @@ out:
return ret;
}
static int icnss_vreg_on(struct icnss_priv *priv)
{
int ret = 0;
struct icnss_vreg_info *vreg_info;
int i;
for (i = 0; i < ICNSS_VREG_INFO_SIZE; i++) {
vreg_info = &priv->vreg_info[i];
if (!vreg_info->reg)
continue;
icnss_pr_dbg("Regulator %s being enabled\n", vreg_info->name);
ret = regulator_set_voltage(vreg_info->reg, vreg_info->min_v,
vreg_info->max_v);
if (ret) {
icnss_pr_err("Regulator %s, can't set voltage: min_v: %u, max_v: %u, ret: %d\n",
vreg_info->name, vreg_info->min_v,
vreg_info->max_v, ret);
break;
}
if (vreg_info->load_ua) {
ret = regulator_set_load(vreg_info->reg,
vreg_info->load_ua);
if (ret < 0) {
icnss_pr_err("Regulator %s, can't set load: %u, ret: %d\n",
vreg_info->name,
vreg_info->load_ua, ret);
break;
}
}
ret = regulator_enable(vreg_info->reg);
if (ret) {
icnss_pr_err("Regulator %s, can't enable: %d\n",
vreg_info->name, ret);
break;
}
if (vreg_info->settle_delay)
udelay(vreg_info->settle_delay);
}
if (!ret)
return 0;
for (; i >= 0; i--) {
vreg_info = &priv->vreg_info[i];
if (!vreg_info->reg)
continue;
regulator_disable(vreg_info->reg);
regulator_set_load(vreg_info->reg, 0);
regulator_set_voltage(vreg_info->reg, 0, vreg_info->max_v);
}
return ret;
}
static int icnss_vreg_off(struct icnss_priv *priv)
{
int ret = 0;
struct icnss_vreg_info *vreg_info;
int i;
for (i = ICNSS_VREG_INFO_SIZE - 1; i >= 0; i--) {
vreg_info = &priv->vreg_info[i];
if (!vreg_info->reg)
continue;
icnss_pr_dbg("Regulator %s being disabled\n", vreg_info->name);
ret = regulator_disable(vreg_info->reg);
if (ret)
icnss_pr_err("Regulator %s, can't disable: %d\n",
vreg_info->name, ret);
ret = regulator_set_load(vreg_info->reg, 0);
if (ret < 0)
icnss_pr_err("Regulator %s, can't set load: %d\n",
vreg_info->name, ret);
ret = regulator_set_voltage(vreg_info->reg, 0,
vreg_info->max_v);
if (ret)
icnss_pr_err("Regulator %s, can't set voltage: %d\n",
vreg_info->name, ret);
}
return ret;
}
static int icnss_clk_init(struct icnss_priv *priv)
{
struct icnss_clk_info *clk_info;
int i;
int ret = 0;
for (i = 0; i < ICNSS_CLK_INFO_SIZE; i++) {
clk_info = &priv->clk_info[i];
if (!clk_info->handle)
continue;
icnss_pr_dbg("Clock %s being enabled\n", clk_info->name);
if (clk_info->freq) {
ret = clk_set_rate(clk_info->handle, clk_info->freq);
if (ret) {
icnss_pr_err("Clock %s, can't set frequency: %u, ret: %d\n",
clk_info->name, clk_info->freq,
ret);
break;
}
}
ret = clk_prepare_enable(clk_info->handle);
if (ret) {
icnss_pr_err("Clock %s, can't enable: %d\n",
clk_info->name, ret);
break;
}
}
if (ret == 0)
return 0;
for (; i >= 0; i--) {
clk_info = &priv->clk_info[i];
if (!clk_info->handle)
continue;
clk_disable_unprepare(clk_info->handle);
}
return ret;
}
static int icnss_clk_deinit(struct icnss_priv *priv)
{
struct icnss_clk_info *clk_info;
int i;
for (i = 0; i < ICNSS_CLK_INFO_SIZE; i++) {
clk_info = &priv->clk_info[i];
if (!clk_info->handle)
continue;
icnss_pr_dbg("Clock %s being disabled\n", clk_info->name);
clk_disable_unprepare(clk_info->handle);
}
return 0;
}
static int icnss_hw_power_on(struct icnss_priv *priv)
{
int ret = 0;
unsigned long flags;
icnss_pr_dbg("HW Power on: state: 0x%lx\n", priv->state);
spin_lock_irqsave(&priv->on_off_lock, flags);
spin_lock(&priv->on_off_lock);
if (test_bit(ICNSS_POWER_ON, &priv->state)) {
spin_unlock_irqrestore(&priv->on_off_lock, flags);
spin_unlock(&priv->on_off_lock);
return ret;
}
set_bit(ICNSS_POWER_ON, &priv->state);
spin_unlock_irqrestore(&priv->on_off_lock, flags);
spin_unlock(&priv->on_off_lock);
ret = icnss_vreg_on(priv);
if (ret)
goto out;
ret = icnss_clk_init(priv);
if (ret)
goto vreg_off;
return ret;
vreg_off:
icnss_vreg_off(priv);
out:
clear_bit(ICNSS_POWER_ON, &priv->state);
return ret;
}
static int icnss_hw_power_off(struct icnss_priv *priv)
{
int ret = 0;
unsigned long flags;
if (test_bit(HW_ALWAYS_ON, &quirks))
return 0;
icnss_pr_dbg("HW Power off: 0x%lx\n", priv->state);
spin_lock_irqsave(&priv->on_off_lock, flags);
spin_lock(&priv->on_off_lock);
if (!test_bit(ICNSS_POWER_ON, &priv->state)) {
spin_unlock_irqrestore(&priv->on_off_lock, flags);
spin_unlock(&priv->on_off_lock);
return ret;
}
clear_bit(ICNSS_POWER_ON, &priv->state);
spin_unlock_irqrestore(&priv->on_off_lock, flags);
spin_unlock(&priv->on_off_lock);
icnss_clk_deinit(priv);
ret = icnss_vreg_off(priv);
return ret;
}
@ -1895,6 +2109,8 @@ static int icnss_call_driver_remove(struct icnss_priv *priv)
clear_bit(ICNSS_DRIVER_PROBED, &priv->state);
icnss_hw_power_off(penv);
return 0;
}
@ -1947,8 +2163,6 @@ static int icnss_driver_event_pd_service_down(struct icnss_priv *priv,
icnss_call_driver_remove(priv);
out:
ret = icnss_hw_power_off(priv);
kfree(data);
return ret;
@ -2923,11 +3137,16 @@ int icnss_trigger_recovery(struct device *dev)
}
if (!priv->service_notifier[0].handle) {
icnss_pr_err("Invalid handle during recovery\n");
icnss_pr_err("Invalid handle during recovery, state: 0x%lx\n",
priv->state);
ret = -EINVAL;
goto out;
}
icnss_pr_dbg("Initiate PD restart at WLAN FW, state: 0x%lx\n",
priv->state);
priv->stats.trigger_recovery++;
/*
* Initiate PDR, required only for the first instance
*/
@ -3005,6 +3224,114 @@ static void icnss_smmu_deinit(struct icnss_priv *priv)
priv->smmu_mapping = NULL;
}
static int icnss_get_vreg_info(struct device *dev,
struct icnss_vreg_info *vreg_info)
{
int ret = 0;
char prop_name[MAX_PROP_SIZE];
struct regulator *reg;
const __be32 *prop;
int len = 0;
int i;
reg = devm_regulator_get_optional(dev, vreg_info->name);
if (PTR_ERR(reg) == -EPROBE_DEFER) {
icnss_pr_err("EPROBE_DEFER for regulator: %s\n",
vreg_info->name);
ret = PTR_ERR(reg);
goto out;
}
if (IS_ERR(reg)) {
ret = PTR_ERR(reg);
if (vreg_info->required) {
icnss_pr_err("Regulator %s doesn't exist: %d\n",
vreg_info->name, ret);
goto out;
} else {
icnss_pr_dbg("Optional regulator %s doesn't exist: %d\n",
vreg_info->name, ret);
goto done;
}
}
vreg_info->reg = reg;
snprintf(prop_name, MAX_PROP_SIZE,
"qcom,%s-config", vreg_info->name);
prop = of_get_property(dev->of_node, prop_name, &len);
icnss_pr_dbg("Got regulator config, prop: %s, len: %d\n",
prop_name, len);
if (!prop || len < (2 * sizeof(__be32))) {
icnss_pr_dbg("Property %s %s\n", prop_name,
prop ? "invalid format" : "doesn't exist");
goto done;
}
for (i = 0; (i * sizeof(__be32)) < len; i++) {
switch (i) {
case 0:
vreg_info->min_v = be32_to_cpup(&prop[0]);
break;
case 1:
vreg_info->max_v = be32_to_cpup(&prop[1]);
break;
case 2:
vreg_info->load_ua = be32_to_cpup(&prop[2]);
break;
case 3:
vreg_info->settle_delay = be32_to_cpup(&prop[3]);
break;
default:
icnss_pr_dbg("Property %s, ignoring value at %d\n",
prop_name, i);
break;
}
}
done:
icnss_pr_dbg("Regulator: %s, min_v: %u, max_v: %u, load: %u, delay: %lu\n",
vreg_info->name, vreg_info->min_v, vreg_info->max_v,
vreg_info->load_ua, vreg_info->settle_delay);
return 0;
out:
return ret;
}
static int icnss_get_clk_info(struct device *dev,
struct icnss_clk_info *clk_info)
{
struct clk *handle;
int ret = 0;
handle = devm_clk_get(dev, clk_info->name);
if (IS_ERR(handle)) {
ret = PTR_ERR(handle);
if (clk_info->required) {
icnss_pr_err("Clock %s isn't available: %d\n",
clk_info->name, ret);
goto out;
} else {
icnss_pr_dbg("Ignoring clock %s: %d\n", clk_info->name,
ret);
ret = 0;
goto out;
}
}
icnss_pr_dbg("Clock: %s, freq: %u\n", clk_info->name, clk_info->freq);
clk_info->handle = handle;
out:
return ret;
}
static int icnss_fw_debug_show(struct seq_file *s, void *data)
{
struct icnss_priv *priv = s->private;
@ -3370,6 +3697,7 @@ static int icnss_stats_show(struct seq_file *s, void *data)
ICNSS_STATS_DUMP(s, priv, rejuvenate_ack_req);
ICNSS_STATS_DUMP(s, priv, rejuvenate_ack_resp);
ICNSS_STATS_DUMP(s, priv, rejuvenate_ack_err);
ICNSS_STATS_DUMP(s, priv, trigger_recovery);
seq_puts(s, "\n<------------------ PM stats ------------------->\n");
ICNSS_STATS_DUMP(s, priv, pm_suspend);
@ -3715,6 +4043,21 @@ static int icnss_probe(struct platform_device *pdev)
if (ret == -EPROBE_DEFER)
goto out;
memcpy(priv->vreg_info, icnss_vreg_info, sizeof(icnss_vreg_info));
for (i = 0; i < ICNSS_VREG_INFO_SIZE; i++) {
ret = icnss_get_vreg_info(dev, &priv->vreg_info[i]);
if (ret)
goto out;
}
memcpy(priv->clk_info, icnss_clk_info, sizeof(icnss_clk_info));
for (i = 0; i < ICNSS_CLK_INFO_SIZE; i++) {
ret = icnss_get_clk_info(dev, &priv->clk_info[i]);
if (ret)
goto out;
}
if (of_property_read_bool(pdev->dev.of_node, "qcom,smmu-s1-bypass"))
priv->bypass_s1_smmu = true;