mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 08:38:45 +02:00 
			
		
		
		
	 9abc1eb62a
			
		
	
	
		9abc1eb62a
		
	
	
	
	
		
			
			The TWL6030 has similar clocks, so add support for it. Take care of the resource grouping handling needed. Signed-off-by: Andreas Kemnade <andreas@kemnade.info> Link: https://lore.kernel.org/r/20241014161109.2222-4-andreas@kemnade.info Reviewed-by: Roger Quadros <rogerq@kernel.org> Signed-off-by: Stephen Boyd <sboyd@kernel.org>
		
			
				
	
	
		
			214 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			214 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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 VREG_GRP		0
 | |
| #define TWL6030_CFG_STATE_OFF   0x00
 | |
| #define TWL6030_CFG_STATE_ON    0x01
 | |
| #define TWL6030_CFG_STATE_MASK  0x03
 | |
| #define TWL6030_CFG_STATE_GRP_SHIFT	5
 | |
| #define TWL6030_CFG_STATE_APP_SHIFT	2
 | |
| #define TWL6030_CFG_STATE_APP_MASK	(0x03 << TWL6030_CFG_STATE_APP_SHIFT)
 | |
| #define TWL6030_CFG_STATE_APP(v)	(((v) & TWL6030_CFG_STATE_APP_MASK) >>\
 | |
| 						TWL6030_CFG_STATE_APP_SHIFT)
 | |
| #define P1_GRP BIT(0) /* processor power group */
 | |
| #define P2_GRP BIT(1)
 | |
| #define P3_GRP BIT(2)
 | |
| #define ALL_GRP (P1_GRP | P2_GRP | P3_GRP)
 | |
| 
 | |
| enum twl_type {
 | |
| 	TWL_TYPE_6030,
 | |
| 	TWL_TYPE_6032,
 | |
| };
 | |
| 
 | |
| struct twl_clock_info {
 | |
| 	struct device *dev;
 | |
| 	enum twl_type type;
 | |
| 	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);
 | |
| 
 | |
| 	if (cinfo->type == TWL_TYPE_6030) {
 | |
| 		int grp;
 | |
| 
 | |
| 		grp = twlclk_read(cinfo, TWL_MODULE_PM_RECEIVER, VREG_GRP);
 | |
| 		if (grp < 0)
 | |
| 			return grp;
 | |
| 
 | |
| 		return twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
 | |
| 				    grp << TWL6030_CFG_STATE_GRP_SHIFT |
 | |
| 				    TWL6030_CFG_STATE_ON);
 | |
| 	}
 | |
| 
 | |
| 	return twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
 | |
| 			    TWL6030_CFG_STATE_ON);
 | |
| }
 | |
| 
 | |
| static void twl6032_clks_unprepare(struct clk_hw *hw)
 | |
| {
 | |
| 	struct twl_clock_info *cinfo = to_twl_clks_info(hw);
 | |
| 	int ret;
 | |
| 
 | |
| 	if (cinfo->type == TWL_TYPE_6030)
 | |
| 		ret = twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
 | |
| 				   ALL_GRP << TWL6030_CFG_STATE_GRP_SHIFT |
 | |
| 				   TWL6030_CFG_STATE_OFF);
 | |
| 	else
 | |
| 		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 const struct clk_ops twl6032_clks_ops = {
 | |
| 	.prepare	= twl6032_clks_prepare,
 | |
| 	.unprepare	= twl6032_clks_unprepare,
 | |
| 	.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].type = platform_get_device_id(pdev)->driver_data;
 | |
| 		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 = "twl6030-clk",
 | |
| 		.driver_data = TWL_TYPE_6030,
 | |
| 	}, {
 | |
| 		.name = "twl6032-clk",
 | |
| 		.driver_data = TWL_TYPE_6032,
 | |
| 	}, {
 | |
| 		/* 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");
 |