293 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Clock driver for Palmas device.4 *5 * Copyright (c) 2013, NVIDIA Corporation.6 * Copyright (c) 2013-2014 Texas Instruments, Inc.7 *8 * Author: Laxman Dewangan <ldewangan@nvidia.com>9 * Peter Ujfalusi <peter.ujfalusi@ti.com>10 */11 12#include <linux/clk.h>13#include <linux/clk-provider.h>14#include <linux/mfd/palmas.h>15#include <linux/module.h>16#include <linux/of.h>17#include <linux/platform_device.h>18#include <linux/slab.h>19 20#define PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE1 121#define PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE2 222#define PALMAS_CLOCK_DT_EXT_CONTROL_NSLEEP 323 24struct palmas_clk32k_desc {25 const char *clk_name;26 unsigned int control_reg;27 unsigned int enable_mask;28 unsigned int sleep_mask;29 unsigned int sleep_reqstr_id;30 int delay;31};32 33struct palmas_clock_info {34 struct device *dev;35 struct clk_hw hw;36 struct palmas *palmas;37 const struct palmas_clk32k_desc *clk_desc;38 int ext_control_pin;39};40 41static inline struct palmas_clock_info *to_palmas_clks_info(struct clk_hw *hw)42{43 return container_of(hw, struct palmas_clock_info, hw);44}45 46static unsigned long palmas_clks_recalc_rate(struct clk_hw *hw,47 unsigned long parent_rate)48{49 return 32768;50}51 52static int palmas_clks_prepare(struct clk_hw *hw)53{54 struct palmas_clock_info *cinfo = to_palmas_clks_info(hw);55 int ret;56 57 ret = palmas_update_bits(cinfo->palmas, PALMAS_RESOURCE_BASE,58 cinfo->clk_desc->control_reg,59 cinfo->clk_desc->enable_mask,60 cinfo->clk_desc->enable_mask);61 if (ret < 0)62 dev_err(cinfo->dev, "Reg 0x%02x update failed, %d\n",63 cinfo->clk_desc->control_reg, ret);64 else if (cinfo->clk_desc->delay)65 udelay(cinfo->clk_desc->delay);66 67 return ret;68}69 70static void palmas_clks_unprepare(struct clk_hw *hw)71{72 struct palmas_clock_info *cinfo = to_palmas_clks_info(hw);73 int ret;74 75 /*76 * Clock can be disabled through external pin if it is externally77 * controlled.78 */79 if (cinfo->ext_control_pin)80 return;81 82 ret = palmas_update_bits(cinfo->palmas, PALMAS_RESOURCE_BASE,83 cinfo->clk_desc->control_reg,84 cinfo->clk_desc->enable_mask, 0);85 if (ret < 0)86 dev_err(cinfo->dev, "Reg 0x%02x update failed, %d\n",87 cinfo->clk_desc->control_reg, ret);88}89 90static int palmas_clks_is_prepared(struct clk_hw *hw)91{92 struct palmas_clock_info *cinfo = to_palmas_clks_info(hw);93 int ret;94 u32 val;95 96 if (cinfo->ext_control_pin)97 return 1;98 99 ret = palmas_read(cinfo->palmas, PALMAS_RESOURCE_BASE,100 cinfo->clk_desc->control_reg, &val);101 if (ret < 0) {102 dev_err(cinfo->dev, "Reg 0x%02x read failed, %d\n",103 cinfo->clk_desc->control_reg, ret);104 return ret;105 }106 return !!(val & cinfo->clk_desc->enable_mask);107}108 109static const struct clk_ops palmas_clks_ops = {110 .prepare = palmas_clks_prepare,111 .unprepare = palmas_clks_unprepare,112 .is_prepared = palmas_clks_is_prepared,113 .recalc_rate = palmas_clks_recalc_rate,114};115 116struct palmas_clks_of_match_data {117 struct clk_init_data init;118 const struct palmas_clk32k_desc desc;119};120 121static const struct palmas_clks_of_match_data palmas_of_clk32kg = {122 .init = {123 .name = "clk32kg",124 .ops = &palmas_clks_ops,125 .flags = CLK_IGNORE_UNUSED,126 },127 .desc = {128 .clk_name = "clk32kg",129 .control_reg = PALMAS_CLK32KG_CTRL,130 .enable_mask = PALMAS_CLK32KG_CTRL_MODE_ACTIVE,131 .sleep_mask = PALMAS_CLK32KG_CTRL_MODE_SLEEP,132 .sleep_reqstr_id = PALMAS_EXTERNAL_REQSTR_ID_CLK32KG,133 .delay = 200,134 },135};136 137static const struct palmas_clks_of_match_data palmas_of_clk32kgaudio = {138 .init = {139 .name = "clk32kgaudio",140 .ops = &palmas_clks_ops,141 .flags = CLK_IGNORE_UNUSED,142 },143 .desc = {144 .clk_name = "clk32kgaudio",145 .control_reg = PALMAS_CLK32KGAUDIO_CTRL,146 .enable_mask = PALMAS_CLK32KG_CTRL_MODE_ACTIVE,147 .sleep_mask = PALMAS_CLK32KG_CTRL_MODE_SLEEP,148 .sleep_reqstr_id = PALMAS_EXTERNAL_REQSTR_ID_CLK32KGAUDIO,149 .delay = 200,150 },151};152 153static const struct of_device_id palmas_clks_of_match[] = {154 {155 .compatible = "ti,palmas-clk32kg",156 .data = &palmas_of_clk32kg,157 },158 {159 .compatible = "ti,palmas-clk32kgaudio",160 .data = &palmas_of_clk32kgaudio,161 },162 { },163};164MODULE_DEVICE_TABLE(of, palmas_clks_of_match);165 166static void palmas_clks_get_clk_data(struct platform_device *pdev,167 struct palmas_clock_info *cinfo)168{169 struct device_node *node = pdev->dev.of_node;170 unsigned int prop;171 int ret;172 173 ret = of_property_read_u32(node, "ti,external-sleep-control",174 &prop);175 if (ret)176 return;177 178 switch (prop) {179 case PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE1:180 prop = PALMAS_EXT_CONTROL_ENABLE1;181 break;182 case PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE2:183 prop = PALMAS_EXT_CONTROL_ENABLE2;184 break;185 case PALMAS_CLOCK_DT_EXT_CONTROL_NSLEEP:186 prop = PALMAS_EXT_CONTROL_NSLEEP;187 break;188 default:189 dev_warn(&pdev->dev, "%pOFn: Invalid ext control option: %u\n",190 node, prop);191 prop = 0;192 break;193 }194 cinfo->ext_control_pin = prop;195}196 197static int palmas_clks_init_configure(struct palmas_clock_info *cinfo)198{199 int ret;200 201 ret = palmas_update_bits(cinfo->palmas, PALMAS_RESOURCE_BASE,202 cinfo->clk_desc->control_reg,203 cinfo->clk_desc->sleep_mask, 0);204 if (ret < 0) {205 dev_err(cinfo->dev, "Reg 0x%02x update failed, %d\n",206 cinfo->clk_desc->control_reg, ret);207 return ret;208 }209 210 if (cinfo->ext_control_pin) {211 ret = clk_prepare(cinfo->hw.clk);212 if (ret < 0) {213 dev_err(cinfo->dev, "Clock prep failed, %d\n", ret);214 return ret;215 }216 217 ret = palmas_ext_control_req_config(cinfo->palmas,218 cinfo->clk_desc->sleep_reqstr_id,219 cinfo->ext_control_pin, true);220 if (ret < 0) {221 dev_err(cinfo->dev, "Ext config for %s failed, %d\n",222 cinfo->clk_desc->clk_name, ret);223 clk_unprepare(cinfo->hw.clk);224 return ret;225 }226 }227 228 return ret;229}230static int palmas_clks_probe(struct platform_device *pdev)231{232 struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);233 struct device_node *node = pdev->dev.of_node;234 const struct palmas_clks_of_match_data *match_data;235 struct palmas_clock_info *cinfo;236 int ret;237 238 match_data = of_device_get_match_data(&pdev->dev);239 if (!match_data)240 return 1;241 242 cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL);243 if (!cinfo)244 return -ENOMEM;245 246 palmas_clks_get_clk_data(pdev, cinfo);247 platform_set_drvdata(pdev, cinfo);248 249 cinfo->dev = &pdev->dev;250 cinfo->palmas = palmas;251 252 cinfo->clk_desc = &match_data->desc;253 cinfo->hw.init = &match_data->init;254 ret = devm_clk_hw_register(&pdev->dev, &cinfo->hw);255 if (ret) {256 dev_err(&pdev->dev, "Fail to register clock %s, %d\n",257 match_data->desc.clk_name, ret);258 return ret;259 }260 261 ret = palmas_clks_init_configure(cinfo);262 if (ret < 0) {263 dev_err(&pdev->dev, "Clock config failed, %d\n", ret);264 return ret;265 }266 267 ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &cinfo->hw);268 if (ret < 0)269 dev_err(&pdev->dev, "Fail to add clock driver, %d\n", ret);270 return ret;271}272 273static void palmas_clks_remove(struct platform_device *pdev)274{275 of_clk_del_provider(pdev->dev.of_node);276}277 278static struct platform_driver palmas_clks_driver = {279 .driver = {280 .name = "palmas-clk",281 .of_match_table = palmas_clks_of_match,282 },283 .probe = palmas_clks_probe,284 .remove = palmas_clks_remove,285};286 287module_platform_driver(palmas_clks_driver);288 289MODULE_DESCRIPTION("Clock driver for Palmas Series Devices");290MODULE_ALIAS("platform:palmas-clk");291MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");292MODULE_LICENSE("GPL v2");293