mirror of
https://github.com/torvalds/linux.git
synced 2024-11-18 18:11:56 +00:00
ufs: Add clock initialization support
Add generic clock initialization support for UFSHCD platform driver. The clock info is read from device tree using standard clock bindings. A generic max-clock-frequency-hz property is defined to save information on maximum operating clock frequency the h/w supports. Signed-off-by: Sujit Reddy Thumma <sthumma@codeaurora.org> Signed-off-by: Dolev Raviv <draviv@codeaurora.org> Signed-off-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
parent
aa49761309
commit
c6e79dacd8
@ -21,8 +21,17 @@ Optional properties:
|
||||
- vccq-max-microamp : specifies max. load that can be drawn from vccq supply
|
||||
- vccq2-max-microamp : specifies max. load that can be drawn from vccq2 supply
|
||||
|
||||
- clocks : List of phandle and clock specifier pairs
|
||||
- clock-names : List of clock input name strings sorted in the same
|
||||
order as the clocks property.
|
||||
- max-clock-frequency-hz : List of maximum operating frequency stored in the same
|
||||
order as the clocks property. If this property is not
|
||||
defined or a value in the array is "0" then it is assumed
|
||||
that the frequency is set by the parent clock or a
|
||||
fixed rate clock source.
|
||||
|
||||
Note: If above properties are not defined it can be assumed that the supply
|
||||
regulators are always on.
|
||||
regulators or clocks are always on.
|
||||
|
||||
Example:
|
||||
ufshc@0xfc598000 {
|
||||
@ -37,4 +46,8 @@ Example:
|
||||
vcc-max-microamp = 500000;
|
||||
vccq-max-microamp = 200000;
|
||||
vccq2-max-microamp = 200000;
|
||||
|
||||
clocks = <&core 0>, <&ref 0>, <&iface 0>;
|
||||
clock-names = "core_clk", "ref_clk", "iface_clk";
|
||||
max-clock-frequency-hz = <100000000 19200000 0>;
|
||||
};
|
||||
|
@ -170,6 +170,8 @@ ufshcd_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
return err;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&hba->clk_list_head);
|
||||
|
||||
err = ufshcd_init(hba, mmio_base, pdev->irq);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "Initialization failed\n");
|
||||
|
@ -53,6 +53,71 @@ static struct ufs_hba_variant_ops *get_variant_ops(struct device *dev)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int ufshcd_parse_clock_info(struct ufs_hba *hba)
|
||||
{
|
||||
int ret = 0;
|
||||
int cnt;
|
||||
int i;
|
||||
struct device *dev = hba->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
char *name;
|
||||
u32 *clkfreq = NULL;
|
||||
struct ufs_clk_info *clki;
|
||||
|
||||
if (!np)
|
||||
goto out;
|
||||
|
||||
INIT_LIST_HEAD(&hba->clk_list_head);
|
||||
|
||||
cnt = of_property_count_strings(np, "clock-names");
|
||||
if (!cnt || (cnt == -EINVAL)) {
|
||||
dev_info(dev, "%s: Unable to find clocks, assuming enabled\n",
|
||||
__func__);
|
||||
} else if (cnt < 0) {
|
||||
dev_err(dev, "%s: count clock strings failed, err %d\n",
|
||||
__func__, cnt);
|
||||
ret = cnt;
|
||||
}
|
||||
|
||||
if (cnt <= 0)
|
||||
goto out;
|
||||
|
||||
clkfreq = kzalloc(cnt * sizeof(*clkfreq), GFP_KERNEL);
|
||||
if (!clkfreq) {
|
||||
ret = -ENOMEM;
|
||||
dev_err(dev, "%s: memory alloc failed\n", __func__);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32_array(np,
|
||||
"max-clock-frequency-hz", clkfreq, cnt);
|
||||
if (ret && (ret != -EINVAL)) {
|
||||
dev_err(dev, "%s: invalid max-clock-frequency-hz property, %d\n",
|
||||
__func__, ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
ret = of_property_read_string_index(np,
|
||||
"clock-names", i, (const char **)&name);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
clki = devm_kzalloc(dev, sizeof(*clki), GFP_KERNEL);
|
||||
if (!clki) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
clki->max_freq = clkfreq[i];
|
||||
clki->name = kstrdup(name, GFP_KERNEL);
|
||||
list_add_tail(&clki->list, &hba->clk_list_head);
|
||||
}
|
||||
out:
|
||||
kfree(clkfreq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define MAX_PROP_SIZE 32
|
||||
static int ufshcd_populate_vreg(struct device *dev, const char *name,
|
||||
struct ufs_vreg **out_vreg)
|
||||
@ -266,6 +331,12 @@ static int ufshcd_pltfrm_probe(struct platform_device *pdev)
|
||||
|
||||
hba->vops = get_variant_ops(&pdev->dev);
|
||||
|
||||
err = ufshcd_parse_clock_info(hba);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "%s: clock parse failed %d\n",
|
||||
__func__, err);
|
||||
goto out;
|
||||
}
|
||||
err = ufshcd_parse_regulator_info(hba);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "%s: regulator init failed %d\n",
|
||||
|
@ -3350,6 +3350,80 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ufshcd_setup_clocks(struct ufs_hba *hba, bool on)
|
||||
{
|
||||
int ret = 0;
|
||||
struct ufs_clk_info *clki;
|
||||
struct list_head *head = &hba->clk_list_head;
|
||||
|
||||
if (!head || list_empty(head))
|
||||
goto out;
|
||||
|
||||
list_for_each_entry(clki, head, list) {
|
||||
if (!IS_ERR_OR_NULL(clki->clk)) {
|
||||
if (on && !clki->enabled) {
|
||||
ret = clk_prepare_enable(clki->clk);
|
||||
if (ret) {
|
||||
dev_err(hba->dev, "%s: %s prepare enable failed, %d\n",
|
||||
__func__, clki->name, ret);
|
||||
goto out;
|
||||
}
|
||||
} else if (!on && clki->enabled) {
|
||||
clk_disable_unprepare(clki->clk);
|
||||
}
|
||||
clki->enabled = on;
|
||||
dev_dbg(hba->dev, "%s: clk: %s %sabled\n", __func__,
|
||||
clki->name, on ? "en" : "dis");
|
||||
}
|
||||
}
|
||||
out:
|
||||
if (ret) {
|
||||
list_for_each_entry(clki, head, list) {
|
||||
if (!IS_ERR_OR_NULL(clki->clk) && clki->enabled)
|
||||
clk_disable_unprepare(clki->clk);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ufshcd_init_clocks(struct ufs_hba *hba)
|
||||
{
|
||||
int ret = 0;
|
||||
struct ufs_clk_info *clki;
|
||||
struct device *dev = hba->dev;
|
||||
struct list_head *head = &hba->clk_list_head;
|
||||
|
||||
if (!head || list_empty(head))
|
||||
goto out;
|
||||
|
||||
list_for_each_entry(clki, head, list) {
|
||||
if (!clki->name)
|
||||
continue;
|
||||
|
||||
clki->clk = devm_clk_get(dev, clki->name);
|
||||
if (IS_ERR(clki->clk)) {
|
||||
ret = PTR_ERR(clki->clk);
|
||||
dev_err(dev, "%s: %s clk get failed, %d\n",
|
||||
__func__, clki->name, ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (clki->max_freq) {
|
||||
ret = clk_set_rate(clki->clk, clki->max_freq);
|
||||
if (ret) {
|
||||
dev_err(hba->dev, "%s: %s clk set rate(%dHz) failed, %d\n",
|
||||
__func__, clki->name,
|
||||
clki->max_freq, ret);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
dev_dbg(dev, "%s: clk: %s, rate: %lu\n", __func__,
|
||||
clki->name, clk_get_rate(clki->clk));
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ufshcd_variant_hba_init(struct ufs_hba *hba)
|
||||
{
|
||||
int err = 0;
|
||||
@ -3409,14 +3483,22 @@ static int ufshcd_hba_init(struct ufs_hba *hba)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = ufshcd_init_vreg(hba);
|
||||
err = ufshcd_init_clocks(hba);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = ufshcd_setup_vreg(hba, true);
|
||||
err = ufshcd_setup_clocks(hba, true);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = ufshcd_init_vreg(hba);
|
||||
if (err)
|
||||
goto out_disable_clks;
|
||||
|
||||
err = ufshcd_setup_vreg(hba, true);
|
||||
if (err)
|
||||
goto out_disable_clks;
|
||||
|
||||
err = ufshcd_variant_hba_init(hba);
|
||||
if (err)
|
||||
goto out_disable_vreg;
|
||||
@ -3425,6 +3507,8 @@ static int ufshcd_hba_init(struct ufs_hba *hba)
|
||||
|
||||
out_disable_vreg:
|
||||
ufshcd_setup_vreg(hba, false);
|
||||
out_disable_clks:
|
||||
ufshcd_setup_clocks(hba, false);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
@ -3433,6 +3517,7 @@ static void ufshcd_hba_exit(struct ufs_hba *hba)
|
||||
{
|
||||
ufshcd_variant_hba_exit(hba);
|
||||
ufshcd_setup_vreg(hba, false);
|
||||
ufshcd_setup_clocks(hba, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -155,6 +155,22 @@ struct ufs_dev_cmd {
|
||||
struct ufs_query query;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ufs_clk_info - UFS clock related info
|
||||
* @list: list headed by hba->clk_list_head
|
||||
* @clk: clock node
|
||||
* @name: clock name
|
||||
* @max_freq: maximum frequency supported by the clock
|
||||
* @enabled: variable to check against multiple enable/disable
|
||||
*/
|
||||
struct ufs_clk_info {
|
||||
struct list_head list;
|
||||
struct clk *clk;
|
||||
const char *name;
|
||||
u32 max_freq;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
#define PRE_CHANGE 0
|
||||
#define POST_CHANGE 1
|
||||
/**
|
||||
@ -221,6 +237,7 @@ struct ufs_hba_variant_ops {
|
||||
* @dev_cmd: ufs device management command information
|
||||
* @auto_bkops_enabled: to track whether bkops is enabled in device
|
||||
* @vreg_info: UFS device voltage regulator information
|
||||
* @clk_list_head: UFS host controller clocks list node head
|
||||
*/
|
||||
struct ufs_hba {
|
||||
void __iomem *mmio_base;
|
||||
@ -282,6 +299,7 @@ struct ufs_hba {
|
||||
|
||||
bool auto_bkops_enabled;
|
||||
struct ufs_vreg_info vreg_info;
|
||||
struct list_head clk_list_head;
|
||||
};
|
||||
|
||||
#define ufshcd_writel(hba, val, reg) \
|
||||
|
Loading…
Reference in New Issue
Block a user