Merge branch 'x86-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[linux-2.6-block.git] / drivers / rtc / rtc-sun6i.c
index 72086f0c078262d7d4bf371e2ecef6ccf4c39f66..39cbc1238b92bbb5766d558b53435ae9ffee0ddd 100644 (file)
@@ -20,6 +20,8 @@
  * more details.
  */
 
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
 #include <linux/delay.h>
 #include <linux/err.h>
 #include <linux/fs.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/rtc.h>
+#include <linux/slab.h>
 #include <linux/types.h>
 
 /* Control register */
 #define SUN6I_LOSC_CTRL                                0x0000
+#define SUN6I_LOSC_CTRL_KEY                    (0x16aa << 16)
 #define SUN6I_LOSC_CTRL_ALM_DHMS_ACC           BIT(9)
 #define SUN6I_LOSC_CTRL_RTC_HMS_ACC            BIT(8)
 #define SUN6I_LOSC_CTRL_RTC_YMD_ACC            BIT(7)
+#define SUN6I_LOSC_CTRL_EXT_OSC                        BIT(0)
 #define SUN6I_LOSC_CTRL_ACC_MASK               GENMASK(9, 7)
 
+#define SUN6I_LOSC_CLK_PRESCAL                 0x0008
+
 /* RTC */
 #define SUN6I_RTC_YMD                          0x0010
 #define SUN6I_RTC_HMS                          0x0014
@@ -115,9 +122,134 @@ struct sun6i_rtc_dev {
        int irq;
        unsigned long alarm;
 
+       struct clk_hw hw;
+       struct clk_hw *int_osc;
+       struct clk *losc;
+
        spinlock_t lock;
 };
 
+static struct sun6i_rtc_dev *sun6i_rtc;
+
+static unsigned long sun6i_rtc_osc_recalc_rate(struct clk_hw *hw,
+                                              unsigned long parent_rate)
+{
+       struct sun6i_rtc_dev *rtc = container_of(hw, struct sun6i_rtc_dev, hw);
+       u32 val;
+
+       val = readl(rtc->base + SUN6I_LOSC_CTRL);
+       if (val & SUN6I_LOSC_CTRL_EXT_OSC)
+               return parent_rate;
+
+       val = readl(rtc->base + SUN6I_LOSC_CLK_PRESCAL);
+       val &= GENMASK(4, 0);
+
+       return parent_rate / (val + 1);
+}
+
+static u8 sun6i_rtc_osc_get_parent(struct clk_hw *hw)
+{
+       struct sun6i_rtc_dev *rtc = container_of(hw, struct sun6i_rtc_dev, hw);
+
+       return readl(rtc->base + SUN6I_LOSC_CTRL) & SUN6I_LOSC_CTRL_EXT_OSC;
+}
+
+static int sun6i_rtc_osc_set_parent(struct clk_hw *hw, u8 index)
+{
+       struct sun6i_rtc_dev *rtc = container_of(hw, struct sun6i_rtc_dev, hw);
+       unsigned long flags;
+       u32 val;
+
+       if (index > 1)
+               return -EINVAL;
+
+       spin_lock_irqsave(&rtc->lock, flags);
+       val = readl(rtc->base + SUN6I_LOSC_CTRL);
+       val &= ~SUN6I_LOSC_CTRL_EXT_OSC;
+       val |= SUN6I_LOSC_CTRL_KEY;
+       val |= index ? SUN6I_LOSC_CTRL_EXT_OSC : 0;
+       writel(val, rtc->base + SUN6I_LOSC_CTRL);
+       spin_unlock_irqrestore(&rtc->lock, flags);
+
+       return 0;
+}
+
+static const struct clk_ops sun6i_rtc_osc_ops = {
+       .recalc_rate    = sun6i_rtc_osc_recalc_rate,
+
+       .get_parent     = sun6i_rtc_osc_get_parent,
+       .set_parent     = sun6i_rtc_osc_set_parent,
+};
+
+static void __init sun6i_rtc_clk_init(struct device_node *node)
+{
+       struct clk_hw_onecell_data *clk_data;
+       struct sun6i_rtc_dev *rtc;
+       struct clk_init_data init = {
+               .ops            = &sun6i_rtc_osc_ops,
+       };
+       const char *parents[2];
+
+       rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
+       if (!rtc)
+               return;
+       spin_lock_init(&rtc->lock);
+
+       clk_data = kzalloc(sizeof(*clk_data) + sizeof(*clk_data->hws),
+                          GFP_KERNEL);
+       if (!clk_data)
+               return;
+       spin_lock_init(&rtc->lock);
+
+       rtc->base = of_io_request_and_map(node, 0, of_node_full_name(node));
+       if (IS_ERR(rtc->base)) {
+               pr_crit("Can't map RTC registers");
+               return;
+       }
+
+       /* Switch to the external, more precise, oscillator */
+       writel(SUN6I_LOSC_CTRL_KEY | SUN6I_LOSC_CTRL_EXT_OSC,
+              rtc->base + SUN6I_LOSC_CTRL);
+
+       /* Yes, I know, this is ugly. */
+       sun6i_rtc = rtc;
+
+       /* Deal with old DTs */
+       if (!of_get_property(node, "clocks", NULL))
+               return;
+
+       rtc->int_osc = clk_hw_register_fixed_rate_with_accuracy(NULL,
+                                                               "rtc-int-osc",
+                                                               NULL, 0,
+                                                               667000,
+                                                               300000000);
+       if (IS_ERR(rtc->int_osc)) {
+               pr_crit("Couldn't register the internal oscillator\n");
+               return;
+       }
+
+       parents[0] = clk_hw_get_name(rtc->int_osc);
+       parents[1] = of_clk_get_parent_name(node, 0);
+
+       rtc->hw.init = &init;
+
+       init.parent_names = parents;
+       init.num_parents = of_clk_get_parent_count(node) + 1;
+       of_property_read_string(node, "clock-output-names", &init.name);
+
+       rtc->losc = clk_register(NULL, &rtc->hw);
+       if (IS_ERR(rtc->losc)) {
+               pr_crit("Couldn't register the LOSC clock\n");
+               return;
+       }
+
+       clk_data->num = 1;
+       clk_data->hws[0] = &rtc->hw;
+       of_clk_add_hw_provider(node, of_clk_hw_onecell_get, clk_data);
+}
+CLK_OF_DECLARE_DRIVER(sun6i_rtc_clk, "allwinner,sun6i-a31-rtc",
+                     sun6i_rtc_clk_init);
+
 static irqreturn_t sun6i_rtc_alarmirq(int irq, void *id)
 {
        struct sun6i_rtc_dev *chip = (struct sun6i_rtc_dev *) id;
@@ -361,23 +493,15 @@ static const struct rtc_class_ops sun6i_rtc_ops = {
 
 static int sun6i_rtc_probe(struct platform_device *pdev)
 {
-       struct sun6i_rtc_dev *chip;
-       struct resource *res;
+       struct sun6i_rtc_dev *chip = sun6i_rtc;
        int ret;
 
-       chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
        if (!chip)
-               return -ENOMEM;
-       spin_lock_init(&chip->lock);
+               return -ENODEV;
 
        platform_set_drvdata(pdev, chip);
        chip->dev = &pdev->dev;
 
-       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       chip->base = devm_ioremap_resource(&pdev->dev, res);
-       if (IS_ERR(chip->base))
-               return PTR_ERR(chip->base);
-
        chip->irq = platform_get_irq(pdev, 0);
        if (chip->irq < 0) {
                dev_err(&pdev->dev, "No IRQ resource\n");
@@ -417,8 +541,10 @@ static int sun6i_rtc_probe(struct platform_device *pdev)
        /* disable alarm wakeup */
        writel(0, chip->base + SUN6I_ALARM_CONFIG);
 
-       chip->rtc = rtc_device_register("rtc-sun6i", &pdev->dev,
-                                       &sun6i_rtc_ops, THIS_MODULE);
+       clk_prepare_enable(chip->losc);
+
+       chip->rtc = devm_rtc_device_register(&pdev->dev, "rtc-sun6i",
+                                            &sun6i_rtc_ops, THIS_MODULE);
        if (IS_ERR(chip->rtc)) {
                dev_err(&pdev->dev, "unable to register device\n");
                return PTR_ERR(chip->rtc);
@@ -429,15 +555,6 @@ static int sun6i_rtc_probe(struct platform_device *pdev)
        return 0;
 }
 
-static int sun6i_rtc_remove(struct platform_device *pdev)
-{
-       struct sun6i_rtc_dev *chip = platform_get_drvdata(pdev);
-
-       rtc_device_unregister(chip->rtc);
-
-       return 0;
-}
-
 static const struct of_device_id sun6i_rtc_dt_ids[] = {
        { .compatible = "allwinner,sun6i-a31-rtc" },
        { /* sentinel */ },
@@ -446,7 +563,6 @@ MODULE_DEVICE_TABLE(of, sun6i_rtc_dt_ids);
 
 static struct platform_driver sun6i_rtc_driver = {
        .probe          = sun6i_rtc_probe,
-       .remove         = sun6i_rtc_remove,
        .driver         = {
                .name           = "sun6i-rtc",
                .of_match_table = sun6i_rtc_dt_ids,