blob: eab9d3c8ed8aee9211a18c40c33b539ee19c746b [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Clock driver for twl device.
*
* inspired by the driver for the Palmas device
*/
#include <linux/clk-provider.h>
#include <linux/mfd/twl.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#define VREG_STATE 2
#define TWL6030_CFG_STATE_OFF 0x00
#define TWL6030_CFG_STATE_ON 0x01
#define TWL6030_CFG_STATE_MASK 0x03
struct twl_clock_info {
struct device *dev;
u8 base;
struct clk_hw hw;
};
static inline int
twlclk_read(struct twl_clock_info *info, unsigned int slave_subgp,
unsigned int offset)
{
u8 value;
int status;
status = twl_i2c_read_u8(slave_subgp, &value,
info->base + offset);
return (status < 0) ? status : value;
}
static inline int
twlclk_write(struct twl_clock_info *info, unsigned int slave_subgp,
unsigned int offset, u8 value)
{
return twl_i2c_write_u8(slave_subgp, value,
info->base + offset);
}
static inline struct twl_clock_info *to_twl_clks_info(struct clk_hw *hw)
{
return container_of(hw, struct twl_clock_info, hw);
}
static unsigned long twl_clks_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return 32768;
}
static int twl6032_clks_prepare(struct clk_hw *hw)
{
struct twl_clock_info *cinfo = to_twl_clks_info(hw);
int ret;
ret = twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
TWL6030_CFG_STATE_ON);
if (ret < 0)
dev_err(cinfo->dev, "clk prepare failed\n");
return ret;
}
static void twl6032_clks_unprepare(struct clk_hw *hw)
{
struct twl_clock_info *cinfo = to_twl_clks_info(hw);
int ret;
ret = twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
TWL6030_CFG_STATE_OFF);
if (ret < 0)
dev_err(cinfo->dev, "clk unprepare failed\n");
}
static int twl6032_clks_is_prepared(struct clk_hw *hw)
{
struct twl_clock_info *cinfo = to_twl_clks_info(hw);
int val;
val = twlclk_read(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE);
if (val < 0) {
dev_err(cinfo->dev, "clk read failed\n");
return val;
}
val &= TWL6030_CFG_STATE_MASK;
return val == TWL6030_CFG_STATE_ON;
}
static const struct clk_ops twl6032_clks_ops = {
.prepare = twl6032_clks_prepare,
.unprepare = twl6032_clks_unprepare,
.is_prepared = twl6032_clks_is_prepared,
.recalc_rate = twl_clks_recalc_rate,
};
struct twl_clks_data {
struct clk_init_data init;
u8 base;
};
static const struct twl_clks_data twl6032_clks[] = {
{
.init = {
.name = "clk32kg",
.ops = &twl6032_clks_ops,
.flags = CLK_IGNORE_UNUSED,
},
.base = 0x8C,
},
{
.init = {
.name = "clk32kaudio",
.ops = &twl6032_clks_ops,
.flags = CLK_IGNORE_UNUSED,
},
.base = 0x8F,
},
{
/* sentinel */
}
};
static int twl_clks_probe(struct platform_device *pdev)
{
struct clk_hw_onecell_data *clk_data;
const struct twl_clks_data *hw_data;
struct twl_clock_info *cinfo;
int ret;
int i;
int count;
hw_data = twl6032_clks;
for (count = 0; hw_data[count].init.name; count++)
;
clk_data = devm_kzalloc(&pdev->dev,
struct_size(clk_data, hws, count),
GFP_KERNEL);
if (!clk_data)
return -ENOMEM;
clk_data->num = count;
cinfo = devm_kcalloc(&pdev->dev, count, sizeof(*cinfo), GFP_KERNEL);
if (!cinfo)
return -ENOMEM;
for (i = 0; i < count; i++) {
cinfo[i].base = hw_data[i].base;
cinfo[i].dev = &pdev->dev;
cinfo[i].hw.init = &hw_data[i].init;
ret = devm_clk_hw_register(&pdev->dev, &cinfo[i].hw);
if (ret) {
return dev_err_probe(&pdev->dev, ret,
"Fail to register clock %s\n",
hw_data[i].init.name);
}
clk_data->hws[i] = &cinfo[i].hw;
}
ret = devm_of_clk_add_hw_provider(&pdev->dev,
of_clk_hw_onecell_get, clk_data);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret,
"Fail to add clock driver\n");
return 0;
}
static const struct platform_device_id twl_clks_id[] = {
{
.name = "twl6032-clk",
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(platform, twl_clks_id);
static struct platform_driver twl_clks_driver = {
.driver = {
.name = "twl-clk",
},
.probe = twl_clks_probe,
.id_table = twl_clks_id,
};
module_platform_driver(twl_clks_driver);
MODULE_DESCRIPTION("Clock driver for TWL Series Devices");
MODULE_LICENSE("GPL");