Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.15.
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Clock driver for twl device.
  4 *
  5 * inspired by the driver for the Palmas device
  6 */
  7
  8#include <linux/clk-provider.h>
  9#include <linux/mfd/twl.h>
 10#include <linux/module.h>
 11#include <linux/platform_device.h>
 12#include <linux/slab.h>
 13
 14#define VREG_STATE		2
 15#define VREG_GRP		0
 16#define TWL6030_CFG_STATE_OFF   0x00
 17#define TWL6030_CFG_STATE_ON    0x01
 18#define TWL6030_CFG_STATE_MASK  0x03
 19#define TWL6030_CFG_STATE_GRP_SHIFT	5
 20#define TWL6030_CFG_STATE_APP_SHIFT	2
 21#define TWL6030_CFG_STATE_APP_MASK	(0x03 << TWL6030_CFG_STATE_APP_SHIFT)
 22#define TWL6030_CFG_STATE_APP(v)	(((v) & TWL6030_CFG_STATE_APP_MASK) >>\
 23						TWL6030_CFG_STATE_APP_SHIFT)
 24#define P1_GRP BIT(0) /* processor power group */
 25#define P2_GRP BIT(1)
 26#define P3_GRP BIT(2)
 27#define ALL_GRP (P1_GRP | P2_GRP | P3_GRP)
 28
 29enum twl_type {
 30	TWL_TYPE_6030,
 31	TWL_TYPE_6032,
 32};
 33
 34struct twl_clock_info {
 35	struct device *dev;
 36	enum twl_type type;
 37	u8 base;
 38	struct clk_hw hw;
 39};
 40
 41static inline int
 42twlclk_read(struct twl_clock_info *info, unsigned int slave_subgp,
 43	    unsigned int offset)
 44{
 45	u8 value;
 46	int status;
 47
 48	status = twl_i2c_read_u8(slave_subgp, &value,
 49				 info->base + offset);
 50	return (status < 0) ? status : value;
 51}
 52
 53static inline int
 54twlclk_write(struct twl_clock_info *info, unsigned int slave_subgp,
 55	     unsigned int offset, u8 value)
 56{
 57	return twl_i2c_write_u8(slave_subgp, value,
 58				info->base + offset);
 59}
 60
 61static inline struct twl_clock_info *to_twl_clks_info(struct clk_hw *hw)
 62{
 63	return container_of(hw, struct twl_clock_info, hw);
 64}
 65
 66static unsigned long twl_clks_recalc_rate(struct clk_hw *hw,
 67					  unsigned long parent_rate)
 68{
 69	return 32768;
 70}
 71
 72static int twl6032_clks_prepare(struct clk_hw *hw)
 73{
 74	struct twl_clock_info *cinfo = to_twl_clks_info(hw);
 75
 76	if (cinfo->type == TWL_TYPE_6030) {
 77		int grp;
 78
 79		grp = twlclk_read(cinfo, TWL_MODULE_PM_RECEIVER, VREG_GRP);
 80		if (grp < 0)
 81			return grp;
 82
 83		return twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
 84				    grp << TWL6030_CFG_STATE_GRP_SHIFT |
 85				    TWL6030_CFG_STATE_ON);
 86	}
 87
 88	return twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
 89			    TWL6030_CFG_STATE_ON);
 90}
 91
 92static void twl6032_clks_unprepare(struct clk_hw *hw)
 93{
 94	struct twl_clock_info *cinfo = to_twl_clks_info(hw);
 95	int ret;
 96
 97	if (cinfo->type == TWL_TYPE_6030)
 98		ret = twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
 99				   ALL_GRP << TWL6030_CFG_STATE_GRP_SHIFT |
100				   TWL6030_CFG_STATE_OFF);
101	else
102		ret = twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
103				   TWL6030_CFG_STATE_OFF);
104
105	if (ret < 0)
106		dev_err(cinfo->dev, "clk unprepare failed\n");
107}
108
109static const struct clk_ops twl6032_clks_ops = {
110	.prepare	= twl6032_clks_prepare,
111	.unprepare	= twl6032_clks_unprepare,
112	.recalc_rate	= twl_clks_recalc_rate,
113};
114
115struct twl_clks_data {
116	struct clk_init_data init;
117	u8 base;
118};
119
120static const struct twl_clks_data twl6032_clks[] = {
121	{
122		.init = {
123			.name = "clk32kg",
124			.ops = &twl6032_clks_ops,
125			.flags = CLK_IGNORE_UNUSED,
126		},
127		.base = 0x8C,
128	},
129	{
130		.init = {
131			.name = "clk32kaudio",
132			.ops = &twl6032_clks_ops,
133			.flags = CLK_IGNORE_UNUSED,
134		},
135		.base = 0x8F,
136	},
137	{
138		/* sentinel */
139	}
140};
141
142static int twl_clks_probe(struct platform_device *pdev)
143{
144	struct clk_hw_onecell_data *clk_data;
145	const struct twl_clks_data *hw_data;
146
147	struct twl_clock_info *cinfo;
148	int ret;
149	int i;
150	int count;
151
152	hw_data = twl6032_clks;
153	for (count = 0; hw_data[count].init.name; count++)
154		;
155
156	clk_data = devm_kzalloc(&pdev->dev,
157				struct_size(clk_data, hws, count),
158				GFP_KERNEL);
159	if (!clk_data)
160		return -ENOMEM;
161
162	clk_data->num = count;
163	cinfo = devm_kcalloc(&pdev->dev, count, sizeof(*cinfo), GFP_KERNEL);
164	if (!cinfo)
165		return -ENOMEM;
166
167	for (i = 0; i < count; i++) {
168		cinfo[i].base = hw_data[i].base;
169		cinfo[i].dev = &pdev->dev;
170		cinfo[i].type = platform_get_device_id(pdev)->driver_data;
171		cinfo[i].hw.init = &hw_data[i].init;
172		ret = devm_clk_hw_register(&pdev->dev, &cinfo[i].hw);
173		if (ret) {
174			return dev_err_probe(&pdev->dev, ret,
175					     "Fail to register clock %s\n",
176					     hw_data[i].init.name);
177		}
178		clk_data->hws[i] = &cinfo[i].hw;
179	}
180
181	ret = devm_of_clk_add_hw_provider(&pdev->dev,
182					  of_clk_hw_onecell_get, clk_data);
183	if (ret < 0)
184		return dev_err_probe(&pdev->dev, ret,
185				     "Fail to add clock driver\n");
186
187	return 0;
188}
189
190static const struct platform_device_id twl_clks_id[] = {
191	{
192		.name = "twl6030-clk",
193		.driver_data = TWL_TYPE_6030,
194	}, {
195		.name = "twl6032-clk",
196		.driver_data = TWL_TYPE_6032,
197	}, {
198		/* sentinel */
199	}
200};
201MODULE_DEVICE_TABLE(platform, twl_clks_id);
202
203static struct platform_driver twl_clks_driver = {
204	.driver = {
205		.name = "twl-clk",
206	},
207	.probe = twl_clks_probe,
208	.id_table = twl_clks_id,
209};
210
211module_platform_driver(twl_clks_driver);
212
213MODULE_DESCRIPTION("Clock driver for TWL Series Devices");
214MODULE_LICENSE("GPL");