xref: /linux/drivers/clk/clk-twl6040.c (revision 75bf465f0bc33e9b776a46d6a1b9b990f5fb7c37)
1*2b27bdccSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2f9f8c043SPeter Ujfalusi /*
3f9f8c043SPeter Ujfalusi * TWL6040 clock module driver for OMAP4 McPDM functional clock
4f9f8c043SPeter Ujfalusi *
5f9f8c043SPeter Ujfalusi * Copyright (C) 2012 Texas Instruments Inc.
6f9f8c043SPeter Ujfalusi * Peter Ujfalusi <peter.ujfalusi@ti.com>
7f9f8c043SPeter Ujfalusi */
8f9f8c043SPeter Ujfalusi 
9f9f8c043SPeter Ujfalusi #include <linux/module.h>
10f9f8c043SPeter Ujfalusi #include <linux/slab.h>
11f9f8c043SPeter Ujfalusi #include <linux/platform_device.h>
12f9f8c043SPeter Ujfalusi #include <linux/mfd/twl6040.h>
13f9f8c043SPeter Ujfalusi #include <linux/clk-provider.h>
14f9f8c043SPeter Ujfalusi 
157e37deb7SPeter Ujfalusi struct twl6040_pdmclk {
16f9f8c043SPeter Ujfalusi 	struct twl6040 *twl6040;
17f9f8c043SPeter Ujfalusi 	struct device *dev;
187e37deb7SPeter Ujfalusi 	struct clk_hw pdmclk_hw;
19f9f8c043SPeter Ujfalusi 	int enabled;
20f9f8c043SPeter Ujfalusi };
21f9f8c043SPeter Ujfalusi 
twl6040_pdmclk_is_prepared(struct clk_hw * hw)227e37deb7SPeter Ujfalusi static int twl6040_pdmclk_is_prepared(struct clk_hw *hw)
23f9f8c043SPeter Ujfalusi {
247e37deb7SPeter Ujfalusi 	struct twl6040_pdmclk *pdmclk = container_of(hw, struct twl6040_pdmclk,
257e37deb7SPeter Ujfalusi 						     pdmclk_hw);
267e37deb7SPeter Ujfalusi 
277e37deb7SPeter Ujfalusi 	return pdmclk->enabled;
28f9f8c043SPeter Ujfalusi }
29f9f8c043SPeter Ujfalusi 
twl6040_pdmclk_reset_one_clock(struct twl6040_pdmclk * pdmclk,unsigned int reg)305ae51d67STony Lindgren static int twl6040_pdmclk_reset_one_clock(struct twl6040_pdmclk *pdmclk,
315ae51d67STony Lindgren 					  unsigned int reg)
325ae51d67STony Lindgren {
335ae51d67STony Lindgren 	const u8 reset_mask = TWL6040_HPLLRST;	/* Same for HPPLL and LPPLL */
345ae51d67STony Lindgren 	int ret;
355ae51d67STony Lindgren 
365ae51d67STony Lindgren 	ret = twl6040_set_bits(pdmclk->twl6040, reg, reset_mask);
375ae51d67STony Lindgren 	if (ret < 0)
385ae51d67STony Lindgren 		return ret;
395ae51d67STony Lindgren 
405ae51d67STony Lindgren 	ret = twl6040_clear_bits(pdmclk->twl6040, reg, reset_mask);
415ae51d67STony Lindgren 	if (ret < 0)
425ae51d67STony Lindgren 		return ret;
435ae51d67STony Lindgren 
445ae51d67STony Lindgren 	return 0;
455ae51d67STony Lindgren }
465ae51d67STony Lindgren 
475ae51d67STony Lindgren /*
485ae51d67STony Lindgren  * TWL6040A2 Phoenix Audio IC erratum #6: "PDM Clock Generation Issue At
495ae51d67STony Lindgren  * Cold Temperature". This affects cold boot and deeper idle states it
505ae51d67STony Lindgren  * seems. The workaround consists of resetting HPPLL and LPPLL.
515ae51d67STony Lindgren  */
twl6040_pdmclk_quirk_reset_clocks(struct twl6040_pdmclk * pdmclk)525ae51d67STony Lindgren static int twl6040_pdmclk_quirk_reset_clocks(struct twl6040_pdmclk *pdmclk)
535ae51d67STony Lindgren {
545ae51d67STony Lindgren 	int ret;
555ae51d67STony Lindgren 
565ae51d67STony Lindgren 	ret = twl6040_pdmclk_reset_one_clock(pdmclk, TWL6040_REG_HPPLLCTL);
575ae51d67STony Lindgren 	if (ret)
585ae51d67STony Lindgren 		return ret;
595ae51d67STony Lindgren 
605ae51d67STony Lindgren 	ret = twl6040_pdmclk_reset_one_clock(pdmclk, TWL6040_REG_LPPLLCTL);
615ae51d67STony Lindgren 	if (ret)
625ae51d67STony Lindgren 		return ret;
635ae51d67STony Lindgren 
645ae51d67STony Lindgren 	return 0;
655ae51d67STony Lindgren }
665ae51d67STony Lindgren 
twl6040_pdmclk_prepare(struct clk_hw * hw)677e37deb7SPeter Ujfalusi static int twl6040_pdmclk_prepare(struct clk_hw *hw)
68f9f8c043SPeter Ujfalusi {
697e37deb7SPeter Ujfalusi 	struct twl6040_pdmclk *pdmclk = container_of(hw, struct twl6040_pdmclk,
707e37deb7SPeter Ujfalusi 						     pdmclk_hw);
71f9f8c043SPeter Ujfalusi 	int ret;
72f9f8c043SPeter Ujfalusi 
737e37deb7SPeter Ujfalusi 	ret = twl6040_power(pdmclk->twl6040, 1);
745ae51d67STony Lindgren 	if (ret)
755ae51d67STony Lindgren 		return ret;
765ae51d67STony Lindgren 
775ae51d67STony Lindgren 	ret = twl6040_pdmclk_quirk_reset_clocks(pdmclk);
785ae51d67STony Lindgren 	if (ret)
795ae51d67STony Lindgren 		goto out_err;
805ae51d67STony Lindgren 
817e37deb7SPeter Ujfalusi 	pdmclk->enabled = 1;
82f9f8c043SPeter Ujfalusi 
835ae51d67STony Lindgren 	return 0;
845ae51d67STony Lindgren 
855ae51d67STony Lindgren out_err:
865ae51d67STony Lindgren 	dev_err(pdmclk->dev, "%s: error %i\n", __func__, ret);
875ae51d67STony Lindgren 	twl6040_power(pdmclk->twl6040, 0);
885ae51d67STony Lindgren 
89f9f8c043SPeter Ujfalusi 	return ret;
90f9f8c043SPeter Ujfalusi }
91f9f8c043SPeter Ujfalusi 
twl6040_pdmclk_unprepare(struct clk_hw * hw)927e37deb7SPeter Ujfalusi static void twl6040_pdmclk_unprepare(struct clk_hw *hw)
93f9f8c043SPeter Ujfalusi {
947e37deb7SPeter Ujfalusi 	struct twl6040_pdmclk *pdmclk = container_of(hw, struct twl6040_pdmclk,
957e37deb7SPeter Ujfalusi 						     pdmclk_hw);
96f9f8c043SPeter Ujfalusi 	int ret;
97f9f8c043SPeter Ujfalusi 
987e37deb7SPeter Ujfalusi 	ret = twl6040_power(pdmclk->twl6040, 0);
99f9f8c043SPeter Ujfalusi 	if (!ret)
1007e37deb7SPeter Ujfalusi 		pdmclk->enabled = 0;
1017e37deb7SPeter Ujfalusi 
102f9f8c043SPeter Ujfalusi }
103f9f8c043SPeter Ujfalusi 
twl6040_pdmclk_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)1047e37deb7SPeter Ujfalusi static unsigned long twl6040_pdmclk_recalc_rate(struct clk_hw *hw,
1057e37deb7SPeter Ujfalusi 						unsigned long parent_rate)
1067e37deb7SPeter Ujfalusi {
1077e37deb7SPeter Ujfalusi 	struct twl6040_pdmclk *pdmclk = container_of(hw, struct twl6040_pdmclk,
1087e37deb7SPeter Ujfalusi 						     pdmclk_hw);
1097e37deb7SPeter Ujfalusi 
1107e37deb7SPeter Ujfalusi 	return twl6040_get_sysclk(pdmclk->twl6040);
1117e37deb7SPeter Ujfalusi }
1127e37deb7SPeter Ujfalusi 
1137e37deb7SPeter Ujfalusi static const struct clk_ops twl6040_pdmclk_ops = {
1147e37deb7SPeter Ujfalusi 	.is_prepared = twl6040_pdmclk_is_prepared,
1157e37deb7SPeter Ujfalusi 	.prepare = twl6040_pdmclk_prepare,
1167e37deb7SPeter Ujfalusi 	.unprepare = twl6040_pdmclk_unprepare,
1177e37deb7SPeter Ujfalusi 	.recalc_rate = twl6040_pdmclk_recalc_rate,
118f9f8c043SPeter Ujfalusi };
119f9f8c043SPeter Ujfalusi 
1200777066dSBhumika Goyal static const struct clk_init_data twl6040_pdmclk_init = {
1217e37deb7SPeter Ujfalusi 	.name = "pdmclk",
1227e37deb7SPeter Ujfalusi 	.ops = &twl6040_pdmclk_ops,
1237e37deb7SPeter Ujfalusi 	.flags = CLK_GET_RATE_NOCACHE,
124f9f8c043SPeter Ujfalusi };
125f9f8c043SPeter Ujfalusi 
twl6040_pdmclk_probe(struct platform_device * pdev)1267e37deb7SPeter Ujfalusi static int twl6040_pdmclk_probe(struct platform_device *pdev)
127f9f8c043SPeter Ujfalusi {
128f9f8c043SPeter Ujfalusi 	struct twl6040 *twl6040 = dev_get_drvdata(pdev->dev.parent);
1297e37deb7SPeter Ujfalusi 	struct twl6040_pdmclk *clkdata;
130f5b3715eSStephen Boyd 	int ret;
131f9f8c043SPeter Ujfalusi 
132f9f8c043SPeter Ujfalusi 	clkdata = devm_kzalloc(&pdev->dev, sizeof(*clkdata), GFP_KERNEL);
133f9f8c043SPeter Ujfalusi 	if (!clkdata)
134f9f8c043SPeter Ujfalusi 		return -ENOMEM;
135f9f8c043SPeter Ujfalusi 
136f9f8c043SPeter Ujfalusi 	clkdata->dev = &pdev->dev;
137f9f8c043SPeter Ujfalusi 	clkdata->twl6040 = twl6040;
138f9f8c043SPeter Ujfalusi 
1397e37deb7SPeter Ujfalusi 	clkdata->pdmclk_hw.init = &twl6040_pdmclk_init;
140f5b3715eSStephen Boyd 	ret = devm_clk_hw_register(&pdev->dev, &clkdata->pdmclk_hw);
141f5b3715eSStephen Boyd 	if (ret)
142f5b3715eSStephen Boyd 		return ret;
143f9f8c043SPeter Ujfalusi 
144c0431037SJingoo Han 	platform_set_drvdata(pdev, clkdata);
145f9f8c043SPeter Ujfalusi 
146654dea6eSMatti Vaittinen 	return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_simple_get,
147f5b3715eSStephen Boyd 					   &clkdata->pdmclk_hw);
148f9f8c043SPeter Ujfalusi }
149f9f8c043SPeter Ujfalusi 
1507e37deb7SPeter Ujfalusi static struct platform_driver twl6040_pdmclk_driver = {
151f9f8c043SPeter Ujfalusi 	.driver = {
1527e37deb7SPeter Ujfalusi 		.name = "twl6040-pdmclk",
153f9f8c043SPeter Ujfalusi 	},
1547e37deb7SPeter Ujfalusi 	.probe = twl6040_pdmclk_probe,
155f9f8c043SPeter Ujfalusi };
156f9f8c043SPeter Ujfalusi 
1577e37deb7SPeter Ujfalusi module_platform_driver(twl6040_pdmclk_driver);
158f9f8c043SPeter Ujfalusi 
159f9f8c043SPeter Ujfalusi MODULE_DESCRIPTION("TWL6040 clock driver for McPDM functional clock");
160f9f8c043SPeter Ujfalusi MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
1617e37deb7SPeter Ujfalusi MODULE_ALIAS("platform:twl6040-pdmclk");
162f9f8c043SPeter Ujfalusi MODULE_LICENSE("GPL");
163