linux/drivers/clk/rockchip/clk.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2014 MundoReader S.L.
   3 * Author: Heiko Stuebner <heiko@sntech.de>
   4 *
   5 * Copyright (c) 2016 Rockchip Electronics Co. Ltd.
   6 * Author: Xing Zheng <zhengxing@rock-chips.com>
   7 *
   8 * based on
   9 *
  10 * samsung/clk.c
  11 * Copyright (c) 2013 Samsung Electronics Co., Ltd.
  12 * Copyright (c) 2013 Linaro Ltd.
  13 * Author: Thomas Abraham <thomas.ab@samsung.com>
  14 *
  15 * This program is free software; you can redistribute it and/or modify
  16 * it under the terms of the GNU General Public License as published by
  17 * the Free Software Foundation; either version 2 of the License, or
  18 * (at your option) any later version.
  19 *
  20 * This program is distributed in the hope that it will be useful,
  21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23 * GNU General Public License for more details.
  24 */
  25
  26#include <linux/slab.h>
  27#include <linux/clk.h>
  28#include <linux/clk-provider.h>
  29#include <linux/mfd/syscon.h>
  30#include <linux/regmap.h>
  31#include <linux/reboot.h>
  32#include <linux/rational.h>
  33#include "clk.h"
  34
  35/**
  36 * Register a clock branch.
  37 * Most clock branches have a form like
  38 *
  39 * src1 --|--\
  40 *        |M |--[GATE]-[DIV]-
  41 * src2 --|--/
  42 *
  43 * sometimes without one of those components.
  44 */
  45static struct clk *rockchip_clk_register_branch(const char *name,
  46                const char *const *parent_names, u8 num_parents,
  47                void __iomem *base,
  48                int muxdiv_offset, u8 mux_shift, u8 mux_width, u8 mux_flags,
  49                u8 div_shift, u8 div_width, u8 div_flags,
  50                struct clk_div_table *div_table, int gate_offset,
  51                u8 gate_shift, u8 gate_flags, unsigned long flags,
  52                spinlock_t *lock)
  53{
  54        struct clk *clk;
  55        struct clk_mux *mux = NULL;
  56        struct clk_gate *gate = NULL;
  57        struct clk_divider *div = NULL;
  58        const struct clk_ops *mux_ops = NULL, *div_ops = NULL,
  59                             *gate_ops = NULL;
  60        int ret;
  61
  62        if (num_parents > 1) {
  63                mux = kzalloc(sizeof(*mux), GFP_KERNEL);
  64                if (!mux)
  65                        return ERR_PTR(-ENOMEM);
  66
  67                mux->reg = base + muxdiv_offset;
  68                mux->shift = mux_shift;
  69                mux->mask = BIT(mux_width) - 1;
  70                mux->flags = mux_flags;
  71                mux->lock = lock;
  72                mux_ops = (mux_flags & CLK_MUX_READ_ONLY) ? &clk_mux_ro_ops
  73                                                        : &clk_mux_ops;
  74        }
  75
  76        if (gate_offset >= 0) {
  77                gate = kzalloc(sizeof(*gate), GFP_KERNEL);
  78                if (!gate) {
  79                        ret = -ENOMEM;
  80                        goto err_gate;
  81                }
  82
  83                gate->flags = gate_flags;
  84                gate->reg = base + gate_offset;
  85                gate->bit_idx = gate_shift;
  86                gate->lock = lock;
  87                gate_ops = &clk_gate_ops;
  88        }
  89
  90        if (div_width > 0) {
  91                div = kzalloc(sizeof(*div), GFP_KERNEL);
  92                if (!div) {
  93                        ret = -ENOMEM;
  94                        goto err_div;
  95                }
  96
  97                div->flags = div_flags;
  98                div->reg = base + muxdiv_offset;
  99                div->shift = div_shift;
 100                div->width = div_width;
 101                div->lock = lock;
 102                div->table = div_table;
 103                div_ops = (div_flags & CLK_DIVIDER_READ_ONLY)
 104                                                ? &clk_divider_ro_ops
 105                                                : &clk_divider_ops;
 106        }
 107
 108        clk = clk_register_composite(NULL, name, parent_names, num_parents,
 109                                     mux ? &mux->hw : NULL, mux_ops,
 110                                     div ? &div->hw : NULL, div_ops,
 111                                     gate ? &gate->hw : NULL, gate_ops,
 112                                     flags);
 113
 114        if (IS_ERR(clk)) {
 115                ret = PTR_ERR(clk);
 116                goto err_composite;
 117        }
 118
 119        return clk;
 120err_composite:
 121        kfree(div);
 122err_div:
 123        kfree(gate);
 124err_gate:
 125        kfree(mux);
 126        return ERR_PTR(ret);
 127}
 128
 129struct rockchip_clk_frac {
 130        struct notifier_block                   clk_nb;
 131        struct clk_fractional_divider           div;
 132        struct clk_gate                         gate;
 133
 134        struct clk_mux                          mux;
 135        const struct clk_ops                    *mux_ops;
 136        int                                     mux_frac_idx;
 137
 138        bool                                    rate_change_remuxed;
 139        int                                     rate_change_idx;
 140};
 141
 142#define to_rockchip_clk_frac_nb(nb) \
 143                        container_of(nb, struct rockchip_clk_frac, clk_nb)
 144
 145static int rockchip_clk_frac_notifier_cb(struct notifier_block *nb,
 146                                         unsigned long event, void *data)
 147{
 148        struct clk_notifier_data *ndata = data;
 149        struct rockchip_clk_frac *frac = to_rockchip_clk_frac_nb(nb);
 150        struct clk_mux *frac_mux = &frac->mux;
 151        int ret = 0;
 152
 153        pr_debug("%s: event %lu, old_rate %lu, new_rate: %lu\n",
 154                 __func__, event, ndata->old_rate, ndata->new_rate);
 155        if (event == PRE_RATE_CHANGE) {
 156                frac->rate_change_idx =
 157                                frac->mux_ops->get_parent(&frac_mux->hw);
 158                if (frac->rate_change_idx != frac->mux_frac_idx) {
 159                        frac->mux_ops->set_parent(&frac_mux->hw,
 160                                                  frac->mux_frac_idx);
 161                        frac->rate_change_remuxed = 1;
 162                }
 163        } else if (event == POST_RATE_CHANGE) {
 164                /*
 165                 * The POST_RATE_CHANGE notifier runs directly after the
 166                 * divider clock is set in clk_change_rate, so we'll have
 167                 * remuxed back to the original parent before clk_change_rate
 168                 * reaches the mux itself.
 169                 */
 170                if (frac->rate_change_remuxed) {
 171                        frac->mux_ops->set_parent(&frac_mux->hw,
 172                                                  frac->rate_change_idx);
 173                        frac->rate_change_remuxed = 0;
 174                }
 175        }
 176
 177        return notifier_from_errno(ret);
 178}
 179
 180/**
 181 * fractional divider must set that denominator is 20 times larger than
 182 * numerator to generate precise clock frequency.
 183 */
 184static void rockchip_fractional_approximation(struct clk_hw *hw,
 185                unsigned long rate, unsigned long *parent_rate,
 186                unsigned long *m, unsigned long *n)
 187{
 188        struct clk_fractional_divider *fd = to_clk_fd(hw);
 189        unsigned long p_rate, p_parent_rate;
 190        struct clk_hw *p_parent;
 191        unsigned long scale;
 192
 193        p_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
 194        if ((rate * 20 > p_rate) && (p_rate % rate != 0)) {
 195                p_parent = clk_hw_get_parent(clk_hw_get_parent(hw));
 196                p_parent_rate = clk_hw_get_rate(p_parent);
 197                *parent_rate = p_parent_rate;
 198        }
 199
 200        /*
 201         * Get rate closer to *parent_rate to guarantee there is no overflow
 202         * for m and n. In the result it will be the nearest rate left shifted
 203         * by (scale - fd->nwidth) bits.
 204         */
 205        scale = fls_long(*parent_rate / rate - 1);
 206        if (scale > fd->nwidth)
 207                rate <<= scale - fd->nwidth;
 208
 209        rational_best_approximation(rate, *parent_rate,
 210                        GENMASK(fd->mwidth - 1, 0), GENMASK(fd->nwidth - 1, 0),
 211                        m, n);
 212}
 213
 214static struct clk *rockchip_clk_register_frac_branch(
 215                struct rockchip_clk_provider *ctx, const char *name,
 216                const char *const *parent_names, u8 num_parents,
 217                void __iomem *base, int muxdiv_offset, u8 div_flags,
 218                int gate_offset, u8 gate_shift, u8 gate_flags,
 219                unsigned long flags, struct rockchip_clk_branch *child,
 220                spinlock_t *lock)
 221{
 222        struct rockchip_clk_frac *frac;
 223        struct clk *clk;
 224        struct clk_gate *gate = NULL;
 225        struct clk_fractional_divider *div = NULL;
 226        const struct clk_ops *div_ops = NULL, *gate_ops = NULL;
 227
 228        if (muxdiv_offset < 0)
 229                return ERR_PTR(-EINVAL);
 230
 231        if (child && child->branch_type != branch_mux) {
 232                pr_err("%s: fractional child clock for %s can only be a mux\n",
 233                       __func__, name);
 234                return ERR_PTR(-EINVAL);
 235        }
 236
 237        frac = kzalloc(sizeof(*frac), GFP_KERNEL);
 238        if (!frac)
 239                return ERR_PTR(-ENOMEM);
 240
 241        if (gate_offset >= 0) {
 242                gate = &frac->gate;
 243                gate->flags = gate_flags;
 244                gate->reg = base + gate_offset;
 245                gate->bit_idx = gate_shift;
 246                gate->lock = lock;
 247                gate_ops = &clk_gate_ops;
 248        }
 249
 250        div = &frac->div;
 251        div->flags = div_flags;
 252        div->reg = base + muxdiv_offset;
 253        div->mshift = 16;
 254        div->mwidth = 16;
 255        div->mmask = GENMASK(div->mwidth - 1, 0) << div->mshift;
 256        div->nshift = 0;
 257        div->nwidth = 16;
 258        div->nmask = GENMASK(div->nwidth - 1, 0) << div->nshift;
 259        div->lock = lock;
 260        div->approximation = rockchip_fractional_approximation;
 261        div_ops = &clk_fractional_divider_ops;
 262
 263        clk = clk_register_composite(NULL, name, parent_names, num_parents,
 264                                     NULL, NULL,
 265                                     &div->hw, div_ops,
 266                                     gate ? &gate->hw : NULL, gate_ops,
 267                                     flags | CLK_SET_RATE_UNGATE);
 268        if (IS_ERR(clk)) {
 269                kfree(frac);
 270                return clk;
 271        }
 272
 273        if (child) {
 274                struct clk_mux *frac_mux = &frac->mux;
 275                struct clk_init_data init;
 276                struct clk *mux_clk;
 277                int ret;
 278
 279                frac->mux_frac_idx = match_string(child->parent_names,
 280                                                  child->num_parents, name);
 281                frac->mux_ops = &clk_mux_ops;
 282                frac->clk_nb.notifier_call = rockchip_clk_frac_notifier_cb;
 283
 284                frac_mux->reg = base + child->muxdiv_offset;
 285                frac_mux->shift = child->mux_shift;
 286                frac_mux->mask = BIT(child->mux_width) - 1;
 287                frac_mux->flags = child->mux_flags;
 288                frac_mux->lock = lock;
 289                frac_mux->hw.init = &init;
 290
 291                init.name = child->name;
 292                init.flags = child->flags | CLK_SET_RATE_PARENT;
 293                init.ops = frac->mux_ops;
 294                init.parent_names = child->parent_names;
 295                init.num_parents = child->num_parents;
 296
 297                mux_clk = clk_register(NULL, &frac_mux->hw);
 298                if (IS_ERR(mux_clk)) {
 299                        kfree(frac);
 300                        return clk;
 301                }
 302
 303                rockchip_clk_add_lookup(ctx, mux_clk, child->id);
 304
 305                /* notifier on the fraction divider to catch rate changes */
 306                if (frac->mux_frac_idx >= 0) {
 307                        pr_debug("%s: found fractional parent in mux at pos %d\n",
 308                                 __func__, frac->mux_frac_idx);
 309                        ret = clk_notifier_register(clk, &frac->clk_nb);
 310                        if (ret)
 311                                pr_err("%s: failed to register clock notifier for %s\n",
 312                                                __func__, name);
 313                } else {
 314                        pr_warn("%s: could not find %s as parent of %s, rate changes may not work\n",
 315                                __func__, name, child->name);
 316                }
 317        }
 318
 319        return clk;
 320}
 321
 322static struct clk *rockchip_clk_register_factor_branch(const char *name,
 323                const char *const *parent_names, u8 num_parents,
 324                void __iomem *base, unsigned int mult, unsigned int div,
 325                int gate_offset, u8 gate_shift, u8 gate_flags,
 326                unsigned long flags, spinlock_t *lock)
 327{
 328        struct clk *clk;
 329        struct clk_gate *gate = NULL;
 330        struct clk_fixed_factor *fix = NULL;
 331
 332        /* without gate, register a simple factor clock */
 333        if (gate_offset == 0) {
 334                return clk_register_fixed_factor(NULL, name,
 335                                parent_names[0], flags, mult,
 336                                div);
 337        }
 338
 339        gate = kzalloc(sizeof(*gate), GFP_KERNEL);
 340        if (!gate)
 341                return ERR_PTR(-ENOMEM);
 342
 343        gate->flags = gate_flags;
 344        gate->reg = base + gate_offset;
 345        gate->bit_idx = gate_shift;
 346        gate->lock = lock;
 347
 348        fix = kzalloc(sizeof(*fix), GFP_KERNEL);
 349        if (!fix) {
 350                kfree(gate);
 351                return ERR_PTR(-ENOMEM);
 352        }
 353
 354        fix->mult = mult;
 355        fix->div = div;
 356
 357        clk = clk_register_composite(NULL, name, parent_names, num_parents,
 358                                     NULL, NULL,
 359                                     &fix->hw, &clk_fixed_factor_ops,
 360                                     &gate->hw, &clk_gate_ops, flags);
 361        if (IS_ERR(clk)) {
 362                kfree(fix);
 363                kfree(gate);
 364        }
 365
 366        return clk;
 367}
 368
 369struct rockchip_clk_provider * __init rockchip_clk_init(struct device_node *np,
 370                        void __iomem *base, unsigned long nr_clks)
 371{
 372        struct rockchip_clk_provider *ctx;
 373        struct clk **clk_table;
 374        int i;
 375
 376        ctx = kzalloc(sizeof(struct rockchip_clk_provider), GFP_KERNEL);
 377        if (!ctx)
 378                return ERR_PTR(-ENOMEM);
 379
 380        clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL);
 381        if (!clk_table)
 382                goto err_free;
 383
 384        for (i = 0; i < nr_clks; ++i)
 385                clk_table[i] = ERR_PTR(-ENOENT);
 386
 387        ctx->reg_base = base;
 388        ctx->clk_data.clks = clk_table;
 389        ctx->clk_data.clk_num = nr_clks;
 390        ctx->cru_node = np;
 391        spin_lock_init(&ctx->lock);
 392
 393        ctx->grf = syscon_regmap_lookup_by_phandle(ctx->cru_node,
 394                                                   "rockchip,grf");
 395
 396        return ctx;
 397
 398err_free:
 399        kfree(ctx);
 400        return ERR_PTR(-ENOMEM);
 401}
 402
 403void __init rockchip_clk_of_add_provider(struct device_node *np,
 404                                struct rockchip_clk_provider *ctx)
 405{
 406        if (of_clk_add_provider(np, of_clk_src_onecell_get,
 407                                &ctx->clk_data))
 408                pr_err("%s: could not register clk provider\n", __func__);
 409}
 410
 411void rockchip_clk_add_lookup(struct rockchip_clk_provider *ctx,
 412                             struct clk *clk, unsigned int id)
 413{
 414        if (ctx->clk_data.clks && id)
 415                ctx->clk_data.clks[id] = clk;
 416}
 417
 418void __init rockchip_clk_register_plls(struct rockchip_clk_provider *ctx,
 419                                struct rockchip_pll_clock *list,
 420                                unsigned int nr_pll, int grf_lock_offset)
 421{
 422        struct clk *clk;
 423        int idx;
 424
 425        for (idx = 0; idx < nr_pll; idx++, list++) {
 426                clk = rockchip_clk_register_pll(ctx, list->type, list->name,
 427                                list->parent_names, list->num_parents,
 428                                list->con_offset, grf_lock_offset,
 429                                list->lock_shift, list->mode_offset,
 430                                list->mode_shift, list->rate_table,
 431                                list->flags, list->pll_flags);
 432                if (IS_ERR(clk)) {
 433                        pr_err("%s: failed to register clock %s\n", __func__,
 434                                list->name);
 435                        continue;
 436                }
 437
 438                rockchip_clk_add_lookup(ctx, clk, list->id);
 439        }
 440}
 441
 442void __init rockchip_clk_register_branches(
 443                                      struct rockchip_clk_provider *ctx,
 444                                      struct rockchip_clk_branch *list,
 445                                      unsigned int nr_clk)
 446{
 447        struct clk *clk = NULL;
 448        unsigned int idx;
 449        unsigned long flags;
 450
 451        for (idx = 0; idx < nr_clk; idx++, list++) {
 452                flags = list->flags;
 453
 454                /* catch simple muxes */
 455                switch (list->branch_type) {
 456                case branch_mux:
 457                        clk = clk_register_mux(NULL, list->name,
 458                                list->parent_names, list->num_parents,
 459                                flags, ctx->reg_base + list->muxdiv_offset,
 460                                list->mux_shift, list->mux_width,
 461                                list->mux_flags, &ctx->lock);
 462                        break;
 463                case branch_muxgrf:
 464                        clk = rockchip_clk_register_muxgrf(list->name,
 465                                list->parent_names, list->num_parents,
 466                                flags, ctx->grf, list->muxdiv_offset,
 467                                list->mux_shift, list->mux_width,
 468                                list->mux_flags);
 469                        break;
 470                case branch_divider:
 471                        if (list->div_table)
 472                                clk = clk_register_divider_table(NULL,
 473                                        list->name, list->parent_names[0],
 474                                        flags,
 475                                        ctx->reg_base + list->muxdiv_offset,
 476                                        list->div_shift, list->div_width,
 477                                        list->div_flags, list->div_table,
 478                                        &ctx->lock);
 479                        else
 480                                clk = clk_register_divider(NULL, list->name,
 481                                        list->parent_names[0], flags,
 482                                        ctx->reg_base + list->muxdiv_offset,
 483                                        list->div_shift, list->div_width,
 484                                        list->div_flags, &ctx->lock);
 485                        break;
 486                case branch_fraction_divider:
 487                        clk = rockchip_clk_register_frac_branch(ctx, list->name,
 488                                list->parent_names, list->num_parents,
 489                                ctx->reg_base, list->muxdiv_offset,
 490                                list->div_flags,
 491                                list->gate_offset, list->gate_shift,
 492                                list->gate_flags, flags, list->child,
 493                                &ctx->lock);
 494                        break;
 495                case branch_half_divider:
 496                        clk = rockchip_clk_register_halfdiv(list->name,
 497                                list->parent_names, list->num_parents,
 498                                ctx->reg_base, list->muxdiv_offset,
 499                                list->mux_shift, list->mux_width,
 500                                list->mux_flags, list->div_shift,
 501                                list->div_width, list->div_flags,
 502                                list->gate_offset, list->gate_shift,
 503                                list->gate_flags, flags, &ctx->lock);
 504                        break;
 505                case branch_gate:
 506                        flags |= CLK_SET_RATE_PARENT;
 507
 508                        clk = clk_register_gate(NULL, list->name,
 509                                list->parent_names[0], flags,
 510                                ctx->reg_base + list->gate_offset,
 511                                list->gate_shift, list->gate_flags, &ctx->lock);
 512                        break;
 513                case branch_composite:
 514                        clk = rockchip_clk_register_branch(list->name,
 515                                list->parent_names, list->num_parents,
 516                                ctx->reg_base, list->muxdiv_offset,
 517                                list->mux_shift,
 518                                list->mux_width, list->mux_flags,
 519                                list->div_shift, list->div_width,
 520                                list->div_flags, list->div_table,
 521                                list->gate_offset, list->gate_shift,
 522                                list->gate_flags, flags, &ctx->lock);
 523                        break;
 524                case branch_mmc:
 525                        clk = rockchip_clk_register_mmc(
 526                                list->name,
 527                                list->parent_names, list->num_parents,
 528                                ctx->reg_base + list->muxdiv_offset,
 529                                list->div_shift
 530                        );
 531                        break;
 532                case branch_inverter:
 533                        clk = rockchip_clk_register_inverter(
 534                                list->name, list->parent_names,
 535                                list->num_parents,
 536                                ctx->reg_base + list->muxdiv_offset,
 537                                list->div_shift, list->div_flags, &ctx->lock);
 538                        break;
 539                case branch_factor:
 540                        clk = rockchip_clk_register_factor_branch(
 541                                list->name, list->parent_names,
 542                                list->num_parents, ctx->reg_base,
 543                                list->div_shift, list->div_width,
 544                                list->gate_offset, list->gate_shift,
 545                                list->gate_flags, flags, &ctx->lock);
 546                        break;
 547                case branch_ddrclk:
 548                        clk = rockchip_clk_register_ddrclk(
 549                                list->name, list->flags,
 550                                list->parent_names, list->num_parents,
 551                                list->muxdiv_offset, list->mux_shift,
 552                                list->mux_width, list->div_shift,
 553                                list->div_width, list->div_flags,
 554                                ctx->reg_base, &ctx->lock);
 555                        break;
 556                }
 557
 558                /* none of the cases above matched */
 559                if (!clk) {
 560                        pr_err("%s: unknown clock type %d\n",
 561                               __func__, list->branch_type);
 562                        continue;
 563                }
 564
 565                if (IS_ERR(clk)) {
 566                        pr_err("%s: failed to register clock %s: %ld\n",
 567                               __func__, list->name, PTR_ERR(clk));
 568                        continue;
 569                }
 570
 571                rockchip_clk_add_lookup(ctx, clk, list->id);
 572        }
 573}
 574
 575void __init rockchip_clk_register_armclk(struct rockchip_clk_provider *ctx,
 576                        unsigned int lookup_id,
 577                        const char *name, const char *const *parent_names,
 578                        u8 num_parents,
 579                        const struct rockchip_cpuclk_reg_data *reg_data,
 580                        const struct rockchip_cpuclk_rate_table *rates,
 581                        int nrates)
 582{
 583        struct clk *clk;
 584
 585        clk = rockchip_clk_register_cpuclk(name, parent_names, num_parents,
 586                                           reg_data, rates, nrates,
 587                                           ctx->reg_base, &ctx->lock);
 588        if (IS_ERR(clk)) {
 589                pr_err("%s: failed to register clock %s: %ld\n",
 590                       __func__, name, PTR_ERR(clk));
 591                return;
 592        }
 593
 594        rockchip_clk_add_lookup(ctx, clk, lookup_id);
 595}
 596
 597void __init rockchip_clk_protect_critical(const char *const clocks[],
 598                                          int nclocks)
 599{
 600        int i;
 601
 602        /* Protect the clocks that needs to stay on */
 603        for (i = 0; i < nclocks; i++) {
 604                struct clk *clk = __clk_lookup(clocks[i]);
 605
 606                if (clk)
 607                        clk_prepare_enable(clk);
 608        }
 609}
 610
 611static void __iomem *rst_base;
 612static unsigned int reg_restart;
 613static void (*cb_restart)(void);
 614static int rockchip_restart_notify(struct notifier_block *this,
 615                                   unsigned long mode, void *cmd)
 616{
 617        if (cb_restart)
 618                cb_restart();
 619
 620        writel(0xfdb9, rst_base + reg_restart);
 621        return NOTIFY_DONE;
 622}
 623
 624static struct notifier_block rockchip_restart_handler = {
 625        .notifier_call = rockchip_restart_notify,
 626        .priority = 128,
 627};
 628
 629void __init
 630rockchip_register_restart_notifier(struct rockchip_clk_provider *ctx,
 631                                               unsigned int reg,
 632                                               void (*cb)(void))
 633{
 634        int ret;
 635
 636        rst_base = ctx->reg_base;
 637        reg_restart = reg;
 638        cb_restart = cb;
 639        ret = register_restart_handler(&rockchip_restart_handler);
 640        if (ret)
 641                pr_err("%s: cannot register restart handler, %d\n",
 642                       __func__, ret);
 643}
 644