#include <linux/init.h>
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/notifier.h>

static struct clk *clk;
static struct notifier_block nb;

static int clk_notif_dbg_cb(struct notifier_block *nb,
		unsigned long event, void *data)
{
	pr_info("clk_notif_dbg_cb\n");

	return NOTIFY_OK;
}

static int clk_notif_dbg_probe(struct platform_device *pdev)
{
	nb.next = NULL;
	nb.notifier_call = clk_notif_dbg_cb;

	dev_info(&pdev->dev, "clk_get()\n");
	clk = clk_get(&pdev->dev, NULL);
	if (IS_ERR(clk)) {
		dev_err(&pdev->dev, "clk_get failed\n");
		return PTR_ERR(clk);
	}
	dev_info(&pdev->dev, "clk_prepare()\n");
	if (clk_prepare(clk))
		dev_warn(&pdev->dev, "clk_prepare failed\n");
	dev_info(&pdev->dev, "clk_enable()\n");
	if (clk_enable(clk))
		dev_warn(&pdev->dev, "clk_enable failed\n");
	dev_info(&pdev->dev, "clk_notifier_register()\n");
	if (clk_notifier_register(clk, &nb))
		dev_warn(&pdev->dev, "clk_notifier_register failed\n");

	dev_info(&pdev->dev, "probe done\n");

	return 0;
}

static int clk_notif_dbg_remove(struct platform_device *pdev)
{
	dev_info(&pdev->dev, "clk_notifier_unregister()\n");
	clk_notifier_unregister(clk, &nb);
	dev_info(&pdev->dev, "clk_disable()\n");
	clk_disable(clk);
	dev_info(&pdev->dev, "clk_unprepare()\n");
	clk_unprepare(clk);
	dev_info(&pdev->dev, "clk_put()\n");
	clk_put(clk);

	dev_info(&pdev->dev, "remove done\n");

	return 0;
}

/* Match table for device tree binding */
static const struct of_device_id clk_notif_dbg_of_match[] = {
	{.compatible = "clk_notif_dbg"},
	{ },
};
MODULE_DEVICE_TABLE(of, clk_notif_dbg_of_match);

static struct platform_driver clk_notif_dbg_driver = {
	.probe		= clk_notif_dbg_probe,
	.remove		= clk_notif_dbg_remove,
	.driver		= {
		.name	= "clk_notif_dbg",
		.owner	= THIS_MODULE,
		.of_match_table = clk_notif_dbg_of_match,
	},
};
module_platform_driver(clk_notif_dbg_driver);

MODULE_AUTHOR("Xilinx, Inc.");
MODULE_DESCRIPTION("Clock notifier debug");
MODULE_LICENSE("GPL");

