Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Copyright (c) 2014 MundoReader S.L.
0004  * Author: Heiko Stuebner <heiko@sntech.de>
0005  *
0006  * Copyright (c) 2016 Rockchip Electronics Co. Ltd.
0007  * Author: Xing Zheng <zhengxing@rock-chips.com>
0008  *
0009  * based on
0010  *
0011  * samsung/clk.c
0012  * Copyright (c) 2013 Samsung Electronics Co., Ltd.
0013  * Copyright (c) 2013 Linaro Ltd.
0014  * Author: Thomas Abraham <thomas.ab@samsung.com>
0015  */
0016 
0017 #include <linux/slab.h>
0018 #include <linux/clk.h>
0019 #include <linux/clk-provider.h>
0020 #include <linux/io.h>
0021 #include <linux/mfd/syscon.h>
0022 #include <linux/regmap.h>
0023 #include <linux/reboot.h>
0024 #include <linux/rational.h>
0025 
0026 #include "../clk-fractional-divider.h"
0027 #include "clk.h"
0028 
0029 /*
0030  * Register a clock branch.
0031  * Most clock branches have a form like
0032  *
0033  * src1 --|--\
0034  *        |M |--[GATE]-[DIV]-
0035  * src2 --|--/
0036  *
0037  * sometimes without one of those components.
0038  */
0039 static struct clk *rockchip_clk_register_branch(const char *name,
0040         const char *const *parent_names, u8 num_parents,
0041         void __iomem *base,
0042         int muxdiv_offset, u8 mux_shift, u8 mux_width, u8 mux_flags,
0043         int div_offset, u8 div_shift, u8 div_width, u8 div_flags,
0044         struct clk_div_table *div_table, int gate_offset,
0045         u8 gate_shift, u8 gate_flags, unsigned long flags,
0046         spinlock_t *lock)
0047 {
0048     struct clk_hw *hw;
0049     struct clk_mux *mux = NULL;
0050     struct clk_gate *gate = NULL;
0051     struct clk_divider *div = NULL;
0052     const struct clk_ops *mux_ops = NULL, *div_ops = NULL,
0053                  *gate_ops = NULL;
0054     int ret;
0055 
0056     if (num_parents > 1) {
0057         mux = kzalloc(sizeof(*mux), GFP_KERNEL);
0058         if (!mux)
0059             return ERR_PTR(-ENOMEM);
0060 
0061         mux->reg = base + muxdiv_offset;
0062         mux->shift = mux_shift;
0063         mux->mask = BIT(mux_width) - 1;
0064         mux->flags = mux_flags;
0065         mux->lock = lock;
0066         mux_ops = (mux_flags & CLK_MUX_READ_ONLY) ? &clk_mux_ro_ops
0067                             : &clk_mux_ops;
0068     }
0069 
0070     if (gate_offset >= 0) {
0071         gate = kzalloc(sizeof(*gate), GFP_KERNEL);
0072         if (!gate) {
0073             ret = -ENOMEM;
0074             goto err_gate;
0075         }
0076 
0077         gate->flags = gate_flags;
0078         gate->reg = base + gate_offset;
0079         gate->bit_idx = gate_shift;
0080         gate->lock = lock;
0081         gate_ops = &clk_gate_ops;
0082     }
0083 
0084     if (div_width > 0) {
0085         div = kzalloc(sizeof(*div), GFP_KERNEL);
0086         if (!div) {
0087             ret = -ENOMEM;
0088             goto err_div;
0089         }
0090 
0091         div->flags = div_flags;
0092         if (div_offset)
0093             div->reg = base + div_offset;
0094         else
0095             div->reg = base + muxdiv_offset;
0096         div->shift = div_shift;
0097         div->width = div_width;
0098         div->lock = lock;
0099         div->table = div_table;
0100         div_ops = (div_flags & CLK_DIVIDER_READ_ONLY)
0101                         ? &clk_divider_ro_ops
0102                         : &clk_divider_ops;
0103     }
0104 
0105     hw = clk_hw_register_composite(NULL, name, parent_names, num_parents,
0106                        mux ? &mux->hw : NULL, mux_ops,
0107                        div ? &div->hw : NULL, div_ops,
0108                        gate ? &gate->hw : NULL, gate_ops,
0109                        flags);
0110     if (IS_ERR(hw)) {
0111         kfree(div);
0112         kfree(gate);
0113         return ERR_CAST(hw);
0114     }
0115 
0116     return hw->clk;
0117 err_div:
0118     kfree(gate);
0119 err_gate:
0120     kfree(mux);
0121     return ERR_PTR(ret);
0122 }
0123 
0124 struct rockchip_clk_frac {
0125     struct notifier_block           clk_nb;
0126     struct clk_fractional_divider       div;
0127     struct clk_gate             gate;
0128 
0129     struct clk_mux              mux;
0130     const struct clk_ops            *mux_ops;
0131     int                 mux_frac_idx;
0132 
0133     bool                    rate_change_remuxed;
0134     int                 rate_change_idx;
0135 };
0136 
0137 #define to_rockchip_clk_frac_nb(nb) \
0138             container_of(nb, struct rockchip_clk_frac, clk_nb)
0139 
0140 static int rockchip_clk_frac_notifier_cb(struct notifier_block *nb,
0141                      unsigned long event, void *data)
0142 {
0143     struct clk_notifier_data *ndata = data;
0144     struct rockchip_clk_frac *frac = to_rockchip_clk_frac_nb(nb);
0145     struct clk_mux *frac_mux = &frac->mux;
0146     int ret = 0;
0147 
0148     pr_debug("%s: event %lu, old_rate %lu, new_rate: %lu\n",
0149          __func__, event, ndata->old_rate, ndata->new_rate);
0150     if (event == PRE_RATE_CHANGE) {
0151         frac->rate_change_idx =
0152                 frac->mux_ops->get_parent(&frac_mux->hw);
0153         if (frac->rate_change_idx != frac->mux_frac_idx) {
0154             frac->mux_ops->set_parent(&frac_mux->hw,
0155                           frac->mux_frac_idx);
0156             frac->rate_change_remuxed = 1;
0157         }
0158     } else if (event == POST_RATE_CHANGE) {
0159         /*
0160          * The POST_RATE_CHANGE notifier runs directly after the
0161          * divider clock is set in clk_change_rate, so we'll have
0162          * remuxed back to the original parent before clk_change_rate
0163          * reaches the mux itself.
0164          */
0165         if (frac->rate_change_remuxed) {
0166             frac->mux_ops->set_parent(&frac_mux->hw,
0167                           frac->rate_change_idx);
0168             frac->rate_change_remuxed = 0;
0169         }
0170     }
0171 
0172     return notifier_from_errno(ret);
0173 }
0174 
0175 /*
0176  * fractional divider must set that denominator is 20 times larger than
0177  * numerator to generate precise clock frequency.
0178  */
0179 static void rockchip_fractional_approximation(struct clk_hw *hw,
0180         unsigned long rate, unsigned long *parent_rate,
0181         unsigned long *m, unsigned long *n)
0182 {
0183     struct clk_fractional_divider *fd = to_clk_fd(hw);
0184     unsigned long p_rate, p_parent_rate;
0185     struct clk_hw *p_parent;
0186 
0187     p_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
0188     if ((rate * 20 > p_rate) && (p_rate % rate != 0)) {
0189         p_parent = clk_hw_get_parent(clk_hw_get_parent(hw));
0190         p_parent_rate = clk_hw_get_rate(p_parent);
0191         *parent_rate = p_parent_rate;
0192     }
0193 
0194     fd->flags |= CLK_FRAC_DIVIDER_POWER_OF_TWO_PS;
0195 
0196     clk_fractional_divider_general_approximation(hw, rate, parent_rate, m, n);
0197 }
0198 
0199 static struct clk *rockchip_clk_register_frac_branch(
0200         struct rockchip_clk_provider *ctx, const char *name,
0201         const char *const *parent_names, u8 num_parents,
0202         void __iomem *base, int muxdiv_offset, u8 div_flags,
0203         int gate_offset, u8 gate_shift, u8 gate_flags,
0204         unsigned long flags, struct rockchip_clk_branch *child,
0205         spinlock_t *lock)
0206 {
0207     struct clk_hw *hw;
0208     struct rockchip_clk_frac *frac;
0209     struct clk_gate *gate = NULL;
0210     struct clk_fractional_divider *div = NULL;
0211     const struct clk_ops *div_ops = NULL, *gate_ops = NULL;
0212 
0213     if (muxdiv_offset < 0)
0214         return ERR_PTR(-EINVAL);
0215 
0216     if (child && child->branch_type != branch_mux) {
0217         pr_err("%s: fractional child clock for %s can only be a mux\n",
0218                __func__, name);
0219         return ERR_PTR(-EINVAL);
0220     }
0221 
0222     frac = kzalloc(sizeof(*frac), GFP_KERNEL);
0223     if (!frac)
0224         return ERR_PTR(-ENOMEM);
0225 
0226     if (gate_offset >= 0) {
0227         gate = &frac->gate;
0228         gate->flags = gate_flags;
0229         gate->reg = base + gate_offset;
0230         gate->bit_idx = gate_shift;
0231         gate->lock = lock;
0232         gate_ops = &clk_gate_ops;
0233     }
0234 
0235     div = &frac->div;
0236     div->flags = div_flags;
0237     div->reg = base + muxdiv_offset;
0238     div->mshift = 16;
0239     div->mwidth = 16;
0240     div->mmask = GENMASK(div->mwidth - 1, 0) << div->mshift;
0241     div->nshift = 0;
0242     div->nwidth = 16;
0243     div->nmask = GENMASK(div->nwidth - 1, 0) << div->nshift;
0244     div->lock = lock;
0245     div->approximation = rockchip_fractional_approximation;
0246     div_ops = &clk_fractional_divider_ops;
0247 
0248     hw = clk_hw_register_composite(NULL, name, parent_names, num_parents,
0249                        NULL, NULL,
0250                        &div->hw, div_ops,
0251                        gate ? &gate->hw : NULL, gate_ops,
0252                        flags | CLK_SET_RATE_UNGATE);
0253     if (IS_ERR(hw)) {
0254         kfree(frac);
0255         return ERR_CAST(hw);
0256     }
0257 
0258     if (child) {
0259         struct clk_mux *frac_mux = &frac->mux;
0260         struct clk_init_data init;
0261         struct clk *mux_clk;
0262         int ret;
0263 
0264         frac->mux_frac_idx = match_string(child->parent_names,
0265                           child->num_parents, name);
0266         frac->mux_ops = &clk_mux_ops;
0267         frac->clk_nb.notifier_call = rockchip_clk_frac_notifier_cb;
0268 
0269         frac_mux->reg = base + child->muxdiv_offset;
0270         frac_mux->shift = child->mux_shift;
0271         frac_mux->mask = BIT(child->mux_width) - 1;
0272         frac_mux->flags = child->mux_flags;
0273         frac_mux->lock = lock;
0274         frac_mux->hw.init = &init;
0275 
0276         init.name = child->name;
0277         init.flags = child->flags | CLK_SET_RATE_PARENT;
0278         init.ops = frac->mux_ops;
0279         init.parent_names = child->parent_names;
0280         init.num_parents = child->num_parents;
0281 
0282         mux_clk = clk_register(NULL, &frac_mux->hw);
0283         if (IS_ERR(mux_clk)) {
0284             kfree(frac);
0285             return mux_clk;
0286         }
0287 
0288         rockchip_clk_add_lookup(ctx, mux_clk, child->id);
0289 
0290         /* notifier on the fraction divider to catch rate changes */
0291         if (frac->mux_frac_idx >= 0) {
0292             pr_debug("%s: found fractional parent in mux at pos %d\n",
0293                  __func__, frac->mux_frac_idx);
0294             ret = clk_notifier_register(hw->clk, &frac->clk_nb);
0295             if (ret)
0296                 pr_err("%s: failed to register clock notifier for %s\n",
0297                         __func__, name);
0298         } else {
0299             pr_warn("%s: could not find %s as parent of %s, rate changes may not work\n",
0300                 __func__, name, child->name);
0301         }
0302     }
0303 
0304     return hw->clk;
0305 }
0306 
0307 static struct clk *rockchip_clk_register_factor_branch(const char *name,
0308         const char *const *parent_names, u8 num_parents,
0309         void __iomem *base, unsigned int mult, unsigned int div,
0310         int gate_offset, u8 gate_shift, u8 gate_flags,
0311         unsigned long flags, spinlock_t *lock)
0312 {
0313     struct clk_hw *hw;
0314     struct clk_gate *gate = NULL;
0315     struct clk_fixed_factor *fix = NULL;
0316 
0317     /* without gate, register a simple factor clock */
0318     if (gate_offset == 0) {
0319         return clk_register_fixed_factor(NULL, name,
0320                 parent_names[0], flags, mult,
0321                 div);
0322     }
0323 
0324     gate = kzalloc(sizeof(*gate), GFP_KERNEL);
0325     if (!gate)
0326         return ERR_PTR(-ENOMEM);
0327 
0328     gate->flags = gate_flags;
0329     gate->reg = base + gate_offset;
0330     gate->bit_idx = gate_shift;
0331     gate->lock = lock;
0332 
0333     fix = kzalloc(sizeof(*fix), GFP_KERNEL);
0334     if (!fix) {
0335         kfree(gate);
0336         return ERR_PTR(-ENOMEM);
0337     }
0338 
0339     fix->mult = mult;
0340     fix->div = div;
0341 
0342     hw = clk_hw_register_composite(NULL, name, parent_names, num_parents,
0343                        NULL, NULL,
0344                        &fix->hw, &clk_fixed_factor_ops,
0345                        &gate->hw, &clk_gate_ops, flags);
0346     if (IS_ERR(hw)) {
0347         kfree(fix);
0348         kfree(gate);
0349         return ERR_CAST(hw);
0350     }
0351 
0352     return hw->clk;
0353 }
0354 
0355 struct rockchip_clk_provider *rockchip_clk_init(struct device_node *np,
0356                         void __iomem *base,
0357                         unsigned long nr_clks)
0358 {
0359     struct rockchip_clk_provider *ctx;
0360     struct clk **clk_table;
0361     int i;
0362 
0363     ctx = kzalloc(sizeof(struct rockchip_clk_provider), GFP_KERNEL);
0364     if (!ctx)
0365         return ERR_PTR(-ENOMEM);
0366 
0367     clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL);
0368     if (!clk_table)
0369         goto err_free;
0370 
0371     for (i = 0; i < nr_clks; ++i)
0372         clk_table[i] = ERR_PTR(-ENOENT);
0373 
0374     ctx->reg_base = base;
0375     ctx->clk_data.clks = clk_table;
0376     ctx->clk_data.clk_num = nr_clks;
0377     ctx->cru_node = np;
0378     spin_lock_init(&ctx->lock);
0379 
0380     ctx->grf = syscon_regmap_lookup_by_phandle(ctx->cru_node,
0381                            "rockchip,grf");
0382 
0383     return ctx;
0384 
0385 err_free:
0386     kfree(ctx);
0387     return ERR_PTR(-ENOMEM);
0388 }
0389 EXPORT_SYMBOL_GPL(rockchip_clk_init);
0390 
0391 void rockchip_clk_of_add_provider(struct device_node *np,
0392                   struct rockchip_clk_provider *ctx)
0393 {
0394     if (of_clk_add_provider(np, of_clk_src_onecell_get,
0395                 &ctx->clk_data))
0396         pr_err("%s: could not register clk provider\n", __func__);
0397 }
0398 EXPORT_SYMBOL_GPL(rockchip_clk_of_add_provider);
0399 
0400 void rockchip_clk_add_lookup(struct rockchip_clk_provider *ctx,
0401                  struct clk *clk, unsigned int id)
0402 {
0403     if (ctx->clk_data.clks && id)
0404         ctx->clk_data.clks[id] = clk;
0405 }
0406 EXPORT_SYMBOL_GPL(rockchip_clk_add_lookup);
0407 
0408 void rockchip_clk_register_plls(struct rockchip_clk_provider *ctx,
0409                 struct rockchip_pll_clock *list,
0410                 unsigned int nr_pll, int grf_lock_offset)
0411 {
0412     struct clk *clk;
0413     int idx;
0414 
0415     for (idx = 0; idx < nr_pll; idx++, list++) {
0416         clk = rockchip_clk_register_pll(ctx, list->type, list->name,
0417                 list->parent_names, list->num_parents,
0418                 list->con_offset, grf_lock_offset,
0419                 list->lock_shift, list->mode_offset,
0420                 list->mode_shift, list->rate_table,
0421                 list->flags, list->pll_flags);
0422         if (IS_ERR(clk)) {
0423             pr_err("%s: failed to register clock %s\n", __func__,
0424                 list->name);
0425             continue;
0426         }
0427 
0428         rockchip_clk_add_lookup(ctx, clk, list->id);
0429     }
0430 }
0431 EXPORT_SYMBOL_GPL(rockchip_clk_register_plls);
0432 
0433 void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
0434                     struct rockchip_clk_branch *list,
0435                     unsigned int nr_clk)
0436 {
0437     struct clk *clk = NULL;
0438     unsigned int idx;
0439     unsigned long flags;
0440 
0441     for (idx = 0; idx < nr_clk; idx++, list++) {
0442         flags = list->flags;
0443 
0444         /* catch simple muxes */
0445         switch (list->branch_type) {
0446         case branch_mux:
0447             clk = clk_register_mux(NULL, list->name,
0448                 list->parent_names, list->num_parents,
0449                 flags, ctx->reg_base + list->muxdiv_offset,
0450                 list->mux_shift, list->mux_width,
0451                 list->mux_flags, &ctx->lock);
0452             break;
0453         case branch_muxgrf:
0454             clk = rockchip_clk_register_muxgrf(list->name,
0455                 list->parent_names, list->num_parents,
0456                 flags, ctx->grf, list->muxdiv_offset,
0457                 list->mux_shift, list->mux_width,
0458                 list->mux_flags);
0459             break;
0460         case branch_divider:
0461             if (list->div_table)
0462                 clk = clk_register_divider_table(NULL,
0463                     list->name, list->parent_names[0],
0464                     flags,
0465                     ctx->reg_base + list->muxdiv_offset,
0466                     list->div_shift, list->div_width,
0467                     list->div_flags, list->div_table,
0468                     &ctx->lock);
0469             else
0470                 clk = clk_register_divider(NULL, list->name,
0471                     list->parent_names[0], flags,
0472                     ctx->reg_base + list->muxdiv_offset,
0473                     list->div_shift, list->div_width,
0474                     list->div_flags, &ctx->lock);
0475             break;
0476         case branch_fraction_divider:
0477             clk = rockchip_clk_register_frac_branch(ctx, list->name,
0478                 list->parent_names, list->num_parents,
0479                 ctx->reg_base, list->muxdiv_offset,
0480                 list->div_flags,
0481                 list->gate_offset, list->gate_shift,
0482                 list->gate_flags, flags, list->child,
0483                 &ctx->lock);
0484             break;
0485         case branch_half_divider:
0486             clk = rockchip_clk_register_halfdiv(list->name,
0487                 list->parent_names, list->num_parents,
0488                 ctx->reg_base, list->muxdiv_offset,
0489                 list->mux_shift, list->mux_width,
0490                 list->mux_flags, list->div_shift,
0491                 list->div_width, list->div_flags,
0492                 list->gate_offset, list->gate_shift,
0493                 list->gate_flags, flags, &ctx->lock);
0494             break;
0495         case branch_gate:
0496             flags |= CLK_SET_RATE_PARENT;
0497 
0498             clk = clk_register_gate(NULL, list->name,
0499                 list->parent_names[0], flags,
0500                 ctx->reg_base + list->gate_offset,
0501                 list->gate_shift, list->gate_flags, &ctx->lock);
0502             break;
0503         case branch_composite:
0504             clk = rockchip_clk_register_branch(list->name,
0505                 list->parent_names, list->num_parents,
0506                 ctx->reg_base, list->muxdiv_offset,
0507                 list->mux_shift,
0508                 list->mux_width, list->mux_flags,
0509                 list->div_offset, list->div_shift, list->div_width,
0510                 list->div_flags, list->div_table,
0511                 list->gate_offset, list->gate_shift,
0512                 list->gate_flags, flags, &ctx->lock);
0513             break;
0514         case branch_mmc:
0515             clk = rockchip_clk_register_mmc(
0516                 list->name,
0517                 list->parent_names, list->num_parents,
0518                 ctx->reg_base + list->muxdiv_offset,
0519                 list->div_shift
0520             );
0521             break;
0522         case branch_inverter:
0523             clk = rockchip_clk_register_inverter(
0524                 list->name, list->parent_names,
0525                 list->num_parents,
0526                 ctx->reg_base + list->muxdiv_offset,
0527                 list->div_shift, list->div_flags, &ctx->lock);
0528             break;
0529         case branch_factor:
0530             clk = rockchip_clk_register_factor_branch(
0531                 list->name, list->parent_names,
0532                 list->num_parents, ctx->reg_base,
0533                 list->div_shift, list->div_width,
0534                 list->gate_offset, list->gate_shift,
0535                 list->gate_flags, flags, &ctx->lock);
0536             break;
0537         case branch_ddrclk:
0538             clk = rockchip_clk_register_ddrclk(
0539                 list->name, list->flags,
0540                 list->parent_names, list->num_parents,
0541                 list->muxdiv_offset, list->mux_shift,
0542                 list->mux_width, list->div_shift,
0543                 list->div_width, list->div_flags,
0544                 ctx->reg_base, &ctx->lock);
0545             break;
0546         }
0547 
0548         /* none of the cases above matched */
0549         if (!clk) {
0550             pr_err("%s: unknown clock type %d\n",
0551                    __func__, list->branch_type);
0552             continue;
0553         }
0554 
0555         if (IS_ERR(clk)) {
0556             pr_err("%s: failed to register clock %s: %ld\n",
0557                    __func__, list->name, PTR_ERR(clk));
0558             continue;
0559         }
0560 
0561         rockchip_clk_add_lookup(ctx, clk, list->id);
0562     }
0563 }
0564 EXPORT_SYMBOL_GPL(rockchip_clk_register_branches);
0565 
0566 void rockchip_clk_register_armclk(struct rockchip_clk_provider *ctx,
0567                   unsigned int lookup_id,
0568                   const char *name, const char *const *parent_names,
0569                   u8 num_parents,
0570                   const struct rockchip_cpuclk_reg_data *reg_data,
0571                   const struct rockchip_cpuclk_rate_table *rates,
0572                   int nrates)
0573 {
0574     struct clk *clk;
0575 
0576     clk = rockchip_clk_register_cpuclk(name, parent_names, num_parents,
0577                        reg_data, rates, nrates,
0578                        ctx->reg_base, &ctx->lock);
0579     if (IS_ERR(clk)) {
0580         pr_err("%s: failed to register clock %s: %ld\n",
0581                __func__, name, PTR_ERR(clk));
0582         return;
0583     }
0584 
0585     rockchip_clk_add_lookup(ctx, clk, lookup_id);
0586 }
0587 EXPORT_SYMBOL_GPL(rockchip_clk_register_armclk);
0588 
0589 void rockchip_clk_protect_critical(const char *const clocks[],
0590                    int nclocks)
0591 {
0592     int i;
0593 
0594     /* Protect the clocks that needs to stay on */
0595     for (i = 0; i < nclocks; i++) {
0596         struct clk *clk = __clk_lookup(clocks[i]);
0597 
0598         clk_prepare_enable(clk);
0599     }
0600 }
0601 EXPORT_SYMBOL_GPL(rockchip_clk_protect_critical);
0602 
0603 static void __iomem *rst_base;
0604 static unsigned int reg_restart;
0605 static void (*cb_restart)(void);
0606 static int rockchip_restart_notify(struct notifier_block *this,
0607                    unsigned long mode, void *cmd)
0608 {
0609     if (cb_restart)
0610         cb_restart();
0611 
0612     writel(0xfdb9, rst_base + reg_restart);
0613     return NOTIFY_DONE;
0614 }
0615 
0616 static struct notifier_block rockchip_restart_handler = {
0617     .notifier_call = rockchip_restart_notify,
0618     .priority = 128,
0619 };
0620 
0621 void
0622 rockchip_register_restart_notifier(struct rockchip_clk_provider *ctx,
0623                    unsigned int reg,
0624                    void (*cb)(void))
0625 {
0626     int ret;
0627 
0628     rst_base = ctx->reg_base;
0629     reg_restart = reg;
0630     cb_restart = cb;
0631     ret = register_restart_handler(&rockchip_restart_handler);
0632     if (ret)
0633         pr_err("%s: cannot register restart handler, %d\n",
0634                __func__, ret);
0635 }
0636 EXPORT_SYMBOL_GPL(rockchip_register_restart_notifier);