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