forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
430 lines
9.5 KiB
430 lines
9.5 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
// Copyright(c) 2015-17 Intel Corporation |
|
|
|
/* |
|
* skl-ssp-clk.c - ASoC skylake ssp clock driver |
|
*/ |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/err.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/clk-provider.h> |
|
#include <linux/clkdev.h> |
|
#include <sound/intel-nhlt.h> |
|
#include "skl.h" |
|
#include "skl-ssp-clk.h" |
|
#include "skl-topology.h" |
|
|
|
#define to_skl_clk(_hw) container_of(_hw, struct skl_clk, hw) |
|
|
|
struct skl_clk_parent { |
|
struct clk_hw *hw; |
|
struct clk_lookup *lookup; |
|
}; |
|
|
|
struct skl_clk { |
|
struct clk_hw hw; |
|
struct clk_lookup *lookup; |
|
unsigned long rate; |
|
struct skl_clk_pdata *pdata; |
|
u32 id; |
|
}; |
|
|
|
struct skl_clk_data { |
|
struct skl_clk_parent parent[SKL_MAX_CLK_SRC]; |
|
struct skl_clk *clk[SKL_MAX_CLK_CNT]; |
|
u8 avail_clk_cnt; |
|
}; |
|
|
|
static int skl_get_clk_type(u32 index) |
|
{ |
|
switch (index) { |
|
case 0 ... (SKL_SCLK_OFS - 1): |
|
return SKL_MCLK; |
|
|
|
case SKL_SCLK_OFS ... (SKL_SCLKFS_OFS - 1): |
|
return SKL_SCLK; |
|
|
|
case SKL_SCLKFS_OFS ... (SKL_MAX_CLK_CNT - 1): |
|
return SKL_SCLK_FS; |
|
|
|
default: |
|
return -EINVAL; |
|
} |
|
} |
|
|
|
static int skl_get_vbus_id(u32 index, u8 clk_type) |
|
{ |
|
switch (clk_type) { |
|
case SKL_MCLK: |
|
return index; |
|
|
|
case SKL_SCLK: |
|
return index - SKL_SCLK_OFS; |
|
|
|
case SKL_SCLK_FS: |
|
return index - SKL_SCLKFS_OFS; |
|
|
|
default: |
|
return -EINVAL; |
|
} |
|
} |
|
|
|
static void skl_fill_clk_ipc(struct skl_clk_rate_cfg_table *rcfg, u8 clk_type) |
|
{ |
|
struct nhlt_fmt_cfg *fmt_cfg; |
|
union skl_clk_ctrl_ipc *ipc; |
|
struct wav_fmt *wfmt; |
|
|
|
if (!rcfg) |
|
return; |
|
|
|
ipc = &rcfg->dma_ctl_ipc; |
|
if (clk_type == SKL_SCLK_FS) { |
|
fmt_cfg = (struct nhlt_fmt_cfg *)rcfg->config; |
|
wfmt = &fmt_cfg->fmt_ext.fmt; |
|
|
|
/* Remove TLV Header size */ |
|
ipc->sclk_fs.hdr.size = sizeof(struct skl_dmactrl_sclkfs_cfg) - |
|
sizeof(struct skl_tlv_hdr); |
|
ipc->sclk_fs.sampling_frequency = wfmt->samples_per_sec; |
|
ipc->sclk_fs.bit_depth = wfmt->bits_per_sample; |
|
ipc->sclk_fs.valid_bit_depth = |
|
fmt_cfg->fmt_ext.sample.valid_bits_per_sample; |
|
ipc->sclk_fs.number_of_channels = wfmt->channels; |
|
} else { |
|
ipc->mclk.hdr.type = DMA_CLK_CONTROLS; |
|
/* Remove TLV Header size */ |
|
ipc->mclk.hdr.size = sizeof(struct skl_dmactrl_mclk_cfg) - |
|
sizeof(struct skl_tlv_hdr); |
|
} |
|
} |
|
|
|
/* Sends dma control IPC to turn the clock ON/OFF */ |
|
static int skl_send_clk_dma_control(struct skl_dev *skl, |
|
struct skl_clk_rate_cfg_table *rcfg, |
|
u32 vbus_id, u8 clk_type, |
|
bool enable) |
|
{ |
|
struct nhlt_specific_cfg *sp_cfg; |
|
u32 i2s_config_size, node_id = 0; |
|
struct nhlt_fmt_cfg *fmt_cfg; |
|
union skl_clk_ctrl_ipc *ipc; |
|
void *i2s_config = NULL; |
|
u8 *data, size; |
|
int ret; |
|
|
|
if (!rcfg) |
|
return -EIO; |
|
|
|
ipc = &rcfg->dma_ctl_ipc; |
|
fmt_cfg = (struct nhlt_fmt_cfg *)rcfg->config; |
|
sp_cfg = &fmt_cfg->config; |
|
|
|
if (clk_type == SKL_SCLK_FS) { |
|
ipc->sclk_fs.hdr.type = |
|
enable ? DMA_TRANSMITION_START : DMA_TRANSMITION_STOP; |
|
data = (u8 *)&ipc->sclk_fs; |
|
size = sizeof(struct skl_dmactrl_sclkfs_cfg); |
|
} else { |
|
/* 1 to enable mclk, 0 to enable sclk */ |
|
if (clk_type == SKL_SCLK) |
|
ipc->mclk.mclk = 0; |
|
else |
|
ipc->mclk.mclk = 1; |
|
|
|
ipc->mclk.keep_running = enable; |
|
ipc->mclk.warm_up_over = enable; |
|
ipc->mclk.clk_stop_over = !enable; |
|
data = (u8 *)&ipc->mclk; |
|
size = sizeof(struct skl_dmactrl_mclk_cfg); |
|
} |
|
|
|
i2s_config_size = sp_cfg->size + size; |
|
i2s_config = kzalloc(i2s_config_size, GFP_KERNEL); |
|
if (!i2s_config) |
|
return -ENOMEM; |
|
|
|
/* copy blob */ |
|
memcpy(i2s_config, sp_cfg->caps, sp_cfg->size); |
|
|
|
/* copy additional dma controls information */ |
|
memcpy(i2s_config + sp_cfg->size, data, size); |
|
|
|
node_id = ((SKL_DMA_I2S_LINK_INPUT_CLASS << 8) | (vbus_id << 4)); |
|
ret = skl_dsp_set_dma_control(skl, (u32 *)i2s_config, |
|
i2s_config_size, node_id); |
|
kfree(i2s_config); |
|
|
|
return ret; |
|
} |
|
|
|
static struct skl_clk_rate_cfg_table *skl_get_rate_cfg( |
|
struct skl_clk_rate_cfg_table *rcfg, |
|
unsigned long rate) |
|
{ |
|
int i; |
|
|
|
for (i = 0; (i < SKL_MAX_CLK_RATES) && rcfg[i].rate; i++) { |
|
if (rcfg[i].rate == rate) |
|
return &rcfg[i]; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
static int skl_clk_change_status(struct skl_clk *clkdev, |
|
bool enable) |
|
{ |
|
struct skl_clk_rate_cfg_table *rcfg; |
|
int vbus_id, clk_type; |
|
|
|
clk_type = skl_get_clk_type(clkdev->id); |
|
if (clk_type < 0) |
|
return clk_type; |
|
|
|
vbus_id = skl_get_vbus_id(clkdev->id, clk_type); |
|
if (vbus_id < 0) |
|
return vbus_id; |
|
|
|
rcfg = skl_get_rate_cfg(clkdev->pdata->ssp_clks[clkdev->id].rate_cfg, |
|
clkdev->rate); |
|
if (!rcfg) |
|
return -EINVAL; |
|
|
|
return skl_send_clk_dma_control(clkdev->pdata->pvt_data, rcfg, |
|
vbus_id, clk_type, enable); |
|
} |
|
|
|
static int skl_clk_prepare(struct clk_hw *hw) |
|
{ |
|
struct skl_clk *clkdev = to_skl_clk(hw); |
|
|
|
return skl_clk_change_status(clkdev, true); |
|
} |
|
|
|
static void skl_clk_unprepare(struct clk_hw *hw) |
|
{ |
|
struct skl_clk *clkdev = to_skl_clk(hw); |
|
|
|
skl_clk_change_status(clkdev, false); |
|
} |
|
|
|
static int skl_clk_set_rate(struct clk_hw *hw, unsigned long rate, |
|
unsigned long parent_rate) |
|
{ |
|
struct skl_clk *clkdev = to_skl_clk(hw); |
|
struct skl_clk_rate_cfg_table *rcfg; |
|
int clk_type; |
|
|
|
if (!rate) |
|
return -EINVAL; |
|
|
|
rcfg = skl_get_rate_cfg(clkdev->pdata->ssp_clks[clkdev->id].rate_cfg, |
|
rate); |
|
if (!rcfg) |
|
return -EINVAL; |
|
|
|
clk_type = skl_get_clk_type(clkdev->id); |
|
if (clk_type < 0) |
|
return clk_type; |
|
|
|
skl_fill_clk_ipc(rcfg, clk_type); |
|
clkdev->rate = rate; |
|
|
|
return 0; |
|
} |
|
|
|
static unsigned long skl_clk_recalc_rate(struct clk_hw *hw, |
|
unsigned long parent_rate) |
|
{ |
|
struct skl_clk *clkdev = to_skl_clk(hw); |
|
|
|
if (clkdev->rate) |
|
return clkdev->rate; |
|
|
|
return 0; |
|
} |
|
|
|
/* Not supported by clk driver. Implemented to satisfy clk fw */ |
|
static long skl_clk_round_rate(struct clk_hw *hw, unsigned long rate, |
|
unsigned long *parent_rate) |
|
{ |
|
return rate; |
|
} |
|
|
|
/* |
|
* prepare/unprepare are used instead of enable/disable as IPC will be sent |
|
* in non-atomic context. |
|
*/ |
|
static const struct clk_ops skl_clk_ops = { |
|
.prepare = skl_clk_prepare, |
|
.unprepare = skl_clk_unprepare, |
|
.set_rate = skl_clk_set_rate, |
|
.round_rate = skl_clk_round_rate, |
|
.recalc_rate = skl_clk_recalc_rate, |
|
}; |
|
|
|
static void unregister_parent_src_clk(struct skl_clk_parent *pclk, |
|
unsigned int id) |
|
{ |
|
while (id--) { |
|
clkdev_drop(pclk[id].lookup); |
|
clk_hw_unregister_fixed_rate(pclk[id].hw); |
|
} |
|
} |
|
|
|
static void unregister_src_clk(struct skl_clk_data *dclk) |
|
{ |
|
while (dclk->avail_clk_cnt--) |
|
clkdev_drop(dclk->clk[dclk->avail_clk_cnt]->lookup); |
|
} |
|
|
|
static int skl_register_parent_clks(struct device *dev, |
|
struct skl_clk_parent *parent, |
|
struct skl_clk_parent_src *pclk) |
|
{ |
|
int i, ret; |
|
|
|
for (i = 0; i < SKL_MAX_CLK_SRC; i++) { |
|
|
|
/* Register Parent clock */ |
|
parent[i].hw = clk_hw_register_fixed_rate(dev, pclk[i].name, |
|
pclk[i].parent_name, 0, pclk[i].rate); |
|
if (IS_ERR(parent[i].hw)) { |
|
ret = PTR_ERR(parent[i].hw); |
|
goto err; |
|
} |
|
|
|
parent[i].lookup = clkdev_hw_create(parent[i].hw, pclk[i].name, |
|
NULL); |
|
if (!parent[i].lookup) { |
|
clk_hw_unregister_fixed_rate(parent[i].hw); |
|
ret = -ENOMEM; |
|
goto err; |
|
} |
|
} |
|
|
|
return 0; |
|
err: |
|
unregister_parent_src_clk(parent, i); |
|
return ret; |
|
} |
|
|
|
/* Assign fmt_config to clk_data */ |
|
static struct skl_clk *register_skl_clk(struct device *dev, |
|
struct skl_ssp_clk *clk, |
|
struct skl_clk_pdata *clk_pdata, int id) |
|
{ |
|
struct clk_init_data init; |
|
struct skl_clk *clkdev; |
|
int ret; |
|
|
|
clkdev = devm_kzalloc(dev, sizeof(*clkdev), GFP_KERNEL); |
|
if (!clkdev) |
|
return ERR_PTR(-ENOMEM); |
|
|
|
init.name = clk->name; |
|
init.ops = &skl_clk_ops; |
|
init.flags = CLK_SET_RATE_GATE; |
|
init.parent_names = &clk->parent_name; |
|
init.num_parents = 1; |
|
clkdev->hw.init = &init; |
|
clkdev->pdata = clk_pdata; |
|
|
|
clkdev->id = id; |
|
ret = devm_clk_hw_register(dev, &clkdev->hw); |
|
if (ret) { |
|
clkdev = ERR_PTR(ret); |
|
return clkdev; |
|
} |
|
|
|
clkdev->lookup = clkdev_hw_create(&clkdev->hw, init.name, NULL); |
|
if (!clkdev->lookup) |
|
clkdev = ERR_PTR(-ENOMEM); |
|
|
|
return clkdev; |
|
} |
|
|
|
static int skl_clk_dev_probe(struct platform_device *pdev) |
|
{ |
|
struct device *dev = &pdev->dev; |
|
struct device *parent_dev = dev->parent; |
|
struct skl_clk_parent_src *parent_clks; |
|
struct skl_clk_pdata *clk_pdata; |
|
struct skl_clk_data *data; |
|
struct skl_ssp_clk *clks; |
|
int ret, i; |
|
|
|
clk_pdata = dev_get_platdata(&pdev->dev); |
|
parent_clks = clk_pdata->parent_clks; |
|
clks = clk_pdata->ssp_clks; |
|
if (!parent_clks || !clks) |
|
return -EIO; |
|
|
|
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); |
|
if (!data) |
|
return -ENOMEM; |
|
|
|
/* Register Parent clock */ |
|
ret = skl_register_parent_clks(parent_dev, data->parent, parent_clks); |
|
if (ret < 0) |
|
return ret; |
|
|
|
for (i = 0; i < clk_pdata->num_clks; i++) { |
|
/* |
|
* Only register valid clocks |
|
* i.e. for which nhlt entry is present. |
|
*/ |
|
if (clks[i].rate_cfg[0].rate == 0) |
|
continue; |
|
|
|
data->clk[data->avail_clk_cnt] = register_skl_clk(dev, |
|
&clks[i], clk_pdata, i); |
|
|
|
if (IS_ERR(data->clk[data->avail_clk_cnt])) { |
|
ret = PTR_ERR(data->clk[data->avail_clk_cnt]); |
|
goto err_unreg_skl_clk; |
|
} |
|
|
|
data->avail_clk_cnt++; |
|
} |
|
|
|
platform_set_drvdata(pdev, data); |
|
|
|
return 0; |
|
|
|
err_unreg_skl_clk: |
|
unregister_src_clk(data); |
|
unregister_parent_src_clk(data->parent, SKL_MAX_CLK_SRC); |
|
|
|
return ret; |
|
} |
|
|
|
static int skl_clk_dev_remove(struct platform_device *pdev) |
|
{ |
|
struct skl_clk_data *data; |
|
|
|
data = platform_get_drvdata(pdev); |
|
unregister_src_clk(data); |
|
unregister_parent_src_clk(data->parent, SKL_MAX_CLK_SRC); |
|
|
|
return 0; |
|
} |
|
|
|
static struct platform_driver skl_clk_driver = { |
|
.driver = { |
|
.name = "skl-ssp-clk", |
|
}, |
|
.probe = skl_clk_dev_probe, |
|
.remove = skl_clk_dev_remove, |
|
}; |
|
|
|
module_platform_driver(skl_clk_driver); |
|
|
|
MODULE_DESCRIPTION("Skylake clock driver"); |
|
MODULE_AUTHOR("Jaikrishna Nemallapudi <[email protected]>"); |
|
MODULE_AUTHOR("Subhransu S. Prusty <[email protected]>"); |
|
MODULE_LICENSE("GPL v2"); |
|
MODULE_ALIAS("platform:skl-ssp-clk");
|
|
|