// SPDX-License-Identifier: GPL-2.0 /* * Clock driver for twl device. * * inspired by the driver for the Palmas device */ #include #include #include #include #include #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");