1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * TWL6040 clock module driver for OMAP4 McPDM functional clock 4 * 5 * Copyright (C) 2012 Texas Instruments Inc. 6 * Peter Ujfalusi <peter.ujfalusi@ti.com> 7 */ 8 9 #include <linux/module.h> 10 #include <linux/slab.h> 11 #include <linux/platform_device.h> 12 #include <linux/mfd/twl6040.h> 13 #include <linux/clk-provider.h> 14 15 struct twl6040_pdmclk { 16 struct twl6040 *twl6040; 17 struct device *dev; 18 struct clk_hw pdmclk_hw; 19 int enabled; 20 }; 21 22 static int twl6040_pdmclk_is_prepared(struct clk_hw *hw) 23 { 24 struct twl6040_pdmclk *pdmclk = container_of(hw, struct twl6040_pdmclk, 25 pdmclk_hw); 26 27 return pdmclk->enabled; 28 } 29 30 static int twl6040_pdmclk_reset_one_clock(struct twl6040_pdmclk *pdmclk, 31 unsigned int reg) 32 { 33 const u8 reset_mask = TWL6040_HPLLRST; /* Same for HPPLL and LPPLL */ 34 int ret; 35 36 ret = twl6040_set_bits(pdmclk->twl6040, reg, reset_mask); 37 if (ret < 0) 38 return ret; 39 40 ret = twl6040_clear_bits(pdmclk->twl6040, reg, reset_mask); 41 if (ret < 0) 42 return ret; 43 44 return 0; 45 } 46 47 /* 48 * TWL6040A2 Phoenix Audio IC erratum #6: "PDM Clock Generation Issue At 49 * Cold Temperature". This affects cold boot and deeper idle states it 50 * seems. The workaround consists of resetting HPPLL and LPPLL. 51 */ 52 static int twl6040_pdmclk_quirk_reset_clocks(struct twl6040_pdmclk *pdmclk) 53 { 54 int ret; 55 56 ret = twl6040_pdmclk_reset_one_clock(pdmclk, TWL6040_REG_HPPLLCTL); 57 if (ret) 58 return ret; 59 60 ret = twl6040_pdmclk_reset_one_clock(pdmclk, TWL6040_REG_LPPLLCTL); 61 if (ret) 62 return ret; 63 64 return 0; 65 } 66 67 static int twl6040_pdmclk_prepare(struct clk_hw *hw) 68 { 69 struct twl6040_pdmclk *pdmclk = container_of(hw, struct twl6040_pdmclk, 70 pdmclk_hw); 71 int ret; 72 73 ret = twl6040_power(pdmclk->twl6040, 1); 74 if (ret) 75 return ret; 76 77 ret = twl6040_pdmclk_quirk_reset_clocks(pdmclk); 78 if (ret) 79 goto out_err; 80 81 pdmclk->enabled = 1; 82 83 return 0; 84 85 out_err: 86 dev_err(pdmclk->dev, "%s: error %i\n", __func__, ret); 87 twl6040_power(pdmclk->twl6040, 0); 88 89 return ret; 90 } 91 92 static void twl6040_pdmclk_unprepare(struct clk_hw *hw) 93 { 94 struct twl6040_pdmclk *pdmclk = container_of(hw, struct twl6040_pdmclk, 95 pdmclk_hw); 96 int ret; 97 98 ret = twl6040_power(pdmclk->twl6040, 0); 99 if (!ret) 100 pdmclk->enabled = 0; 101 102 } 103 104 static unsigned long twl6040_pdmclk_recalc_rate(struct clk_hw *hw, 105 unsigned long parent_rate) 106 { 107 struct twl6040_pdmclk *pdmclk = container_of(hw, struct twl6040_pdmclk, 108 pdmclk_hw); 109 110 return twl6040_get_sysclk(pdmclk->twl6040); 111 } 112 113 static const struct clk_ops twl6040_pdmclk_ops = { 114 .is_prepared = twl6040_pdmclk_is_prepared, 115 .prepare = twl6040_pdmclk_prepare, 116 .unprepare = twl6040_pdmclk_unprepare, 117 .recalc_rate = twl6040_pdmclk_recalc_rate, 118 }; 119 120 static const struct clk_init_data twl6040_pdmclk_init = { 121 .name = "pdmclk", 122 .ops = &twl6040_pdmclk_ops, 123 .flags = CLK_GET_RATE_NOCACHE, 124 }; 125 126 static int twl6040_pdmclk_probe(struct platform_device *pdev) 127 { 128 struct twl6040 *twl6040 = dev_get_drvdata(pdev->dev.parent); 129 struct twl6040_pdmclk *clkdata; 130 int ret; 131 132 clkdata = devm_kzalloc(&pdev->dev, sizeof(*clkdata), GFP_KERNEL); 133 if (!clkdata) 134 return -ENOMEM; 135 136 clkdata->dev = &pdev->dev; 137 clkdata->twl6040 = twl6040; 138 139 clkdata->pdmclk_hw.init = &twl6040_pdmclk_init; 140 ret = devm_clk_hw_register(&pdev->dev, &clkdata->pdmclk_hw); 141 if (ret) 142 return ret; 143 144 platform_set_drvdata(pdev, clkdata); 145 146 return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_simple_get, 147 &clkdata->pdmclk_hw); 148 } 149 150 static struct platform_driver twl6040_pdmclk_driver = { 151 .driver = { 152 .name = "twl6040-pdmclk", 153 }, 154 .probe = twl6040_pdmclk_probe, 155 }; 156 157 module_platform_driver(twl6040_pdmclk_driver); 158 159 MODULE_DESCRIPTION("TWL6040 clock driver for McPDM functional clock"); 160 MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); 161 MODULE_ALIAS("platform:twl6040-pdmclk"); 162 MODULE_LICENSE("GPL"); 163