| /* |
| * Copyright (C) 2015 Nest Labs |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation version 2. |
| * |
| * This program is distributed "as is" WITHOUT ANY WARRANTY of any |
| * kind, whether express or implied; without even the implied warranty |
| * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/clk-provider.h> |
| #include <linux/slab.h> |
| #include <linux/err.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/delay.h> |
| |
| struct clk_padctl_gate { |
| struct clk_hw hw; |
| struct pinctrl *pctl; |
| struct pinctrl_state *pstate_enabled; |
| struct pinctrl_state *pstate_disabled; |
| }; |
| |
| #define to_clk_padctl_gate(_hw) container_of(_hw, struct clk_padctl_gate, hw) |
| |
| static int clk_padctl_gate_enable(struct clk_hw *hw) |
| { |
| struct clk_padctl_gate *pg = to_clk_padctl_gate(hw); |
| pinctrl_select_state(pg->pctl, pg->pstate_enabled); |
| udelay(20); |
| return 0; |
| } |
| |
| static void clk_padctl_gate_disable(struct clk_hw *hw) |
| { |
| struct clk_padctl_gate *pg = to_clk_padctl_gate(hw); |
| pinctrl_select_state(pg->pctl, pg->pstate_disabled); |
| } |
| |
| static struct clk_ops clk_padctl_gate_ops = { |
| .enable = clk_padctl_gate_enable, |
| .disable = clk_padctl_gate_disable, |
| }; |
| |
| static int padctl_gate_clk_probe(struct platform_device *pdev) |
| { |
| struct clk *clk; |
| const char *clk_name; |
| const char *parent_name; |
| struct clk_padctl_gate *pg; |
| struct clk_init_data init; |
| |
| /* Use node name unless clock-output-names is specified */ |
| clk_name = pdev->dev.of_node->name; |
| of_property_read_string(pdev->dev.of_node, "clock-output-names", &clk_name); |
| |
| /* Look up name of parent clock */ |
| parent_name = of_clk_get_parent_name(pdev->dev.of_node, 0); |
| if (!parent_name) |
| return -EINVAL; |
| |
| /* Allocate storage for variables */ |
| pg = devm_kzalloc(&pdev->dev, sizeof(*pg), GFP_KERNEL); |
| if (!pg) |
| return -ENOMEM; |
| |
| platform_set_drvdata(pdev, pg); |
| |
| /* Get pin control and settings for enabled and disabled states */ |
| pg->pctl = devm_pinctrl_get(&pdev->dev); |
| if (IS_ERR(pg->pctl)) { |
| dev_err(&pdev->dev, "Cannot get pinctrl: %ld\n", PTR_ERR(pg->pctl)); |
| return PTR_ERR(pg->pctl); |
| } |
| |
| pg->pstate_enabled = pinctrl_lookup_state(pg->pctl, "active"); |
| if (IS_ERR(pg->pstate_enabled)) { |
| dev_err(&pdev->dev, "Cannot get enabled state: %ld\n", PTR_ERR(pg->pstate_enabled)); |
| return PTR_ERR(pg->pstate_enabled); |
| } |
| |
| pg->pstate_disabled = pinctrl_lookup_state(pg->pctl, "default"); |
| if (IS_ERR(pg->pstate_disabled)) { |
| dev_err(&pdev->dev, "Cannot get disabled state: %ld\n", PTR_ERR(pg->pstate_disabled)); |
| return PTR_ERR(pg->pstate_disabled); |
| } |
| |
| /* Set up clock initialization values */ |
| init.name = clk_name; |
| init.ops = &clk_padctl_gate_ops; |
| init.flags = CLK_IS_BASIC; |
| init.parent_names = &parent_name; |
| init.num_parents = 1; |
| |
| pg->hw.init = &init; |
| |
| clk = devm_clk_register(&pdev->dev, &pg->hw); |
| if (IS_ERR(clk)) |
| return PTR_ERR(clk); |
| |
| /* Register this DT node as being associated with a clock */ |
| of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, clk); |
| |
| return 0; |
| } |
| |
| static int padctl_gate_clk_remove(struct platform_device *pdev) |
| { |
| of_clk_del_provider(pdev->dev.of_node); |
| return 0; |
| } |
| |
| static const struct of_device_id padctl_gate_clk_of_match[] = { |
| {.compatible = "padctl-gate-clock",}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, padctl_gate_clk_of_match); |
| |
| /* |
| * Clock driver init functions normally register with CLK_OF_DECLARE |
| * and are added to a table processed by of_clk_init. This function |
| * is called by time_init very early in the boot process and certain |
| * kernel services like padctrl are not available. Also when clocks |
| * are created this way they are not first class devices and device |
| * based operations become unavailable. Given all this, just make |
| * this a platform driver instead. |
| */ |
| |
| static struct platform_driver padctl_gate_clk_driver = { |
| .probe = padctl_gate_clk_probe, |
| .remove = padctl_gate_clk_remove, |
| .driver = { |
| .name = "clk-padctl-gate", |
| .of_match_table = padctl_gate_clk_of_match, |
| }, |
| }; |
| module_platform_driver(padctl_gate_clk_driver); |
| |
| MODULE_AUTHOR("Tim Kryger <tkryger@nestlabs.com>"); |
| MODULE_DESCRIPTION("Pad Control Gate Clock Driver"); |
| MODULE_LICENSE("GPL v2"); |