| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (C) 2016 Free Electrons |
| * |
| * Maxime Ripard <maxime.ripard@free-electrons.com> |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/component.h> |
| #include <linux/module.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| #include <linux/reset.h> |
| |
| struct sun6i_drc { |
| struct clk *bus_clk; |
| struct clk *mod_clk; |
| struct reset_control *reset; |
| }; |
| |
| static int sun6i_drc_bind(struct device *dev, struct device *master, |
| void *data) |
| { |
| struct sun6i_drc *drc; |
| int ret; |
| |
| drc = devm_kzalloc(dev, sizeof(*drc), GFP_KERNEL); |
| if (!drc) |
| return -ENOMEM; |
| dev_set_drvdata(dev, drc); |
| |
| drc->reset = devm_reset_control_get(dev, NULL); |
| if (IS_ERR(drc->reset)) { |
| dev_err(dev, "Couldn't get our reset line\n"); |
| return PTR_ERR(drc->reset); |
| } |
| |
| ret = reset_control_deassert(drc->reset); |
| if (ret) { |
| dev_err(dev, "Couldn't deassert our reset line\n"); |
| return ret; |
| } |
| |
| drc->bus_clk = devm_clk_get(dev, "ahb"); |
| if (IS_ERR(drc->bus_clk)) { |
| dev_err(dev, "Couldn't get our bus clock\n"); |
| ret = PTR_ERR(drc->bus_clk); |
| goto err_assert_reset; |
| } |
| clk_prepare_enable(drc->bus_clk); |
| |
| drc->mod_clk = devm_clk_get(dev, "mod"); |
| if (IS_ERR(drc->mod_clk)) { |
| dev_err(dev, "Couldn't get our mod clock\n"); |
| ret = PTR_ERR(drc->mod_clk); |
| goto err_disable_bus_clk; |
| } |
| |
| ret = clk_set_rate_exclusive(drc->mod_clk, 300000000); |
| if (ret) { |
| dev_err(dev, "Couldn't set the module clock frequency\n"); |
| goto err_disable_bus_clk; |
| } |
| |
| clk_prepare_enable(drc->mod_clk); |
| |
| return 0; |
| |
| err_disable_bus_clk: |
| clk_disable_unprepare(drc->bus_clk); |
| err_assert_reset: |
| reset_control_assert(drc->reset); |
| return ret; |
| } |
| |
| static void sun6i_drc_unbind(struct device *dev, struct device *master, |
| void *data) |
| { |
| struct sun6i_drc *drc = dev_get_drvdata(dev); |
| |
| clk_rate_exclusive_put(drc->mod_clk); |
| clk_disable_unprepare(drc->mod_clk); |
| clk_disable_unprepare(drc->bus_clk); |
| reset_control_assert(drc->reset); |
| } |
| |
| static const struct component_ops sun6i_drc_ops = { |
| .bind = sun6i_drc_bind, |
| .unbind = sun6i_drc_unbind, |
| }; |
| |
| static int sun6i_drc_probe(struct platform_device *pdev) |
| { |
| return component_add(&pdev->dev, &sun6i_drc_ops); |
| } |
| |
| static void sun6i_drc_remove(struct platform_device *pdev) |
| { |
| component_del(&pdev->dev, &sun6i_drc_ops); |
| } |
| |
| static const struct of_device_id sun6i_drc_of_table[] = { |
| { .compatible = "allwinner,sun6i-a31-drc" }, |
| { .compatible = "allwinner,sun6i-a31s-drc" }, |
| { .compatible = "allwinner,sun8i-a23-drc" }, |
| { .compatible = "allwinner,sun8i-a33-drc" }, |
| { .compatible = "allwinner,sun9i-a80-drc" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, sun6i_drc_of_table); |
| |
| static struct platform_driver sun6i_drc_platform_driver = { |
| .probe = sun6i_drc_probe, |
| .remove_new = sun6i_drc_remove, |
| .driver = { |
| .name = "sun6i-drc", |
| .of_match_table = sun6i_drc_of_table, |
| }, |
| }; |
| module_platform_driver(sun6i_drc_platform_driver); |
| |
| MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); |
| MODULE_DESCRIPTION("Allwinner A31 Dynamic Range Control (DRC) Driver"); |
| MODULE_LICENSE("GPL"); |