mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-16 09:48:16 +00:00
505 lines
13 KiB
C
505 lines
13 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
|||
|
/*
|
|||
|
* RZ/G2L Clock Pulse Generator
|
|||
|
*
|
|||
|
* Copyright (C) 2021-2023 Renesas Electronics Corp.
|
|||
|
*
|
|||
|
* Based on renesas-cpg-mssr.c
|
|||
|
*
|
|||
|
* Copyright (C) 2015 Glider bvba
|
|||
|
* Copyright (C) 2013 Ideas On Board SPRL
|
|||
|
* Copyright (C) 2015 Renesas Electronics Corp.
|
|||
|
*/
|
|||
|
|
|||
|
#include <asm/io.h>
|
|||
|
#include <clk-uclass.h>
|
|||
|
#include <dm.h>
|
|||
|
#include <dm/device-internal.h>
|
|||
|
#include <dm/device_compat.h>
|
|||
|
#include <dm/devres.h>
|
|||
|
#include <dm/lists.h>
|
|||
|
#include <dt-bindings/clock/renesas-cpg-mssr.h>
|
|||
|
#include <linux/clk-provider.h>
|
|||
|
#include <linux/iopoll.h>
|
|||
|
#include <reset-uclass.h>
|
|||
|
#include <reset.h>
|
|||
|
|
|||
|
#include "rzg2l-cpg.h"
|
|||
|
|
|||
|
#define CLK_MON_R(reg) (0x180 + (reg))
|
|||
|
|
|||
|
static ulong rzg2l_cpg_clk_get_rate_by_id(struct udevice *dev, unsigned int id);
|
|||
|
static ulong rzg2l_cpg_clk_get_rate_by_name(struct udevice *dev, const char *name);
|
|||
|
|
|||
|
struct rzg2l_cpg_data {
|
|||
|
void __iomem *base;
|
|||
|
struct rzg2l_cpg_info *info;
|
|||
|
};
|
|||
|
|
|||
|
/*
|
|||
|
* The top 16 bits of the clock ID are used to identify if it is a core clock or
|
|||
|
* a module clock.
|
|||
|
*/
|
|||
|
#define CPG_CLK_TYPE_SHIFT 16
|
|||
|
#define CPG_CLK_ID_MASK 0xffff
|
|||
|
#define CPG_CLK_ID(x) ((x) & CPG_CLK_ID_MASK)
|
|||
|
#define CPG_CLK_PACK(type, id) (((type) << CPG_CLK_TYPE_SHIFT) | CPG_CLK_ID(id))
|
|||
|
|
|||
|
static inline bool is_mod_clk(unsigned int id)
|
|||
|
{
|
|||
|
return (id >> CPG_CLK_TYPE_SHIFT) == CPG_MOD;
|
|||
|
}
|
|||
|
|
|||
|
static int rzg2l_cpg_clk_set(struct clk *clk, bool enable)
|
|||
|
{
|
|||
|
struct rzg2l_cpg_data *data =
|
|||
|
(struct rzg2l_cpg_data *)dev_get_driver_data(clk->dev);
|
|||
|
const unsigned int cpg_clk_id = CPG_CLK_ID(clk->id);
|
|||
|
const struct rzg2l_mod_clk *mod_clk = NULL;
|
|||
|
u32 value;
|
|||
|
unsigned int i;
|
|||
|
|
|||
|
dev_dbg(clk->dev, "%s %s clock %u\n", enable ? "enable" : "disable",
|
|||
|
is_mod_clk(clk->id) ? "module" : "core", cpg_clk_id);
|
|||
|
if (!is_mod_clk(clk->id)) {
|
|||
|
dev_err(clk->dev, "ID %lu is not a module clock\n", clk->id);
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
|
|||
|
for (i = 0; i < data->info->num_mod_clks; i++) {
|
|||
|
if (data->info->mod_clks[i].id == cpg_clk_id) {
|
|||
|
mod_clk = &data->info->mod_clks[i];
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!mod_clk) {
|
|||
|
dev_err(clk->dev, "Module clock %u not found\n", cpg_clk_id);
|
|||
|
return -ENODEV;
|
|||
|
}
|
|||
|
|
|||
|
value = BIT(mod_clk->bit) << 16;
|
|||
|
if (enable)
|
|||
|
value |= BIT(mod_clk->bit);
|
|||
|
writel(value, data->base + mod_clk->off);
|
|||
|
|
|||
|
if (enable && readl_poll_timeout(data->base + CLK_MON_R(mod_clk->off),
|
|||
|
value, (value & BIT(mod_clk->bit)),
|
|||
|
10)) {
|
|||
|
dev_err(clk->dev, "Timeout\n");
|
|||
|
return -ETIMEDOUT;
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static int rzg2l_cpg_clk_enable(struct clk *clk)
|
|||
|
{
|
|||
|
return rzg2l_cpg_clk_set(clk, true);
|
|||
|
}
|
|||
|
|
|||
|
static int rzg2l_cpg_clk_disable(struct clk *clk)
|
|||
|
{
|
|||
|
return rzg2l_cpg_clk_set(clk, false);
|
|||
|
}
|
|||
|
|
|||
|
static int rzg2l_cpg_clk_of_xlate(struct clk *clk,
|
|||
|
struct ofnode_phandle_args *args)
|
|||
|
{
|
|||
|
struct rzg2l_cpg_data *data =
|
|||
|
(struct rzg2l_cpg_data *)dev_get_driver_data(clk->dev);
|
|||
|
u32 cpg_clk_type, cpg_clk_id;
|
|||
|
bool found = false;
|
|||
|
unsigned int i;
|
|||
|
|
|||
|
if (args->args_count != 2) {
|
|||
|
dev_dbg(clk->dev, "Invalid args_count: %d\n", args->args_count);
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
|
|||
|
cpg_clk_type = args->args[0];
|
|||
|
cpg_clk_id = args->args[1];
|
|||
|
|
|||
|
switch (cpg_clk_type) {
|
|||
|
case CPG_CORE:
|
|||
|
for (i = 0; i < data->info->num_core_clks; i++) {
|
|||
|
if (data->info->core_clks[i].id == cpg_clk_id) {
|
|||
|
found = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (!found) {
|
|||
|
dev_dbg(clk->dev,
|
|||
|
"Invalid second argument %u: Must be a valid core clock ID\n",
|
|||
|
cpg_clk_id);
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
break;
|
|||
|
case CPG_MOD:
|
|||
|
for (i = 0; i < data->info->num_mod_clks; i++) {
|
|||
|
if (data->info->mod_clks[i].id == cpg_clk_id) {
|
|||
|
found = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (!found) {
|
|||
|
dev_dbg(clk->dev,
|
|||
|
"Invalid second argument %u: Must be a valid module clock ID\n",
|
|||
|
cpg_clk_id);
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
break;
|
|||
|
default:
|
|||
|
dev_dbg(clk->dev,
|
|||
|
"Invalid first argument %u: Must be CPG_CORE or CPG_MOD\n",
|
|||
|
cpg_clk_type);
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
|
|||
|
clk->id = CPG_CLK_PACK(cpg_clk_type, cpg_clk_id);
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static ulong rzg2l_sdhi_clk_get_rate(struct udevice *dev, const struct cpg_core_clk *cc)
|
|||
|
{
|
|||
|
struct rzg2l_cpg_data *data =
|
|||
|
(struct rzg2l_cpg_data *)dev_get_driver_data(dev);
|
|||
|
const ulong offset = CPG_CONF_OFFSET(cc->conf);
|
|||
|
const int shift = CPG_CONF_BITPOS(cc->conf);
|
|||
|
const u32 mask = CPG_CONF_BITMASK(cc->conf);
|
|||
|
unsigned int sel;
|
|||
|
|
|||
|
sel = (readl(data->base + offset) >> shift) & mask;
|
|||
|
|
|||
|
if (!sel || sel > cc->num_parents) {
|
|||
|
dev_err(dev, "Invalid SEL_SDHI%d_SET value %u\n", shift / 4, sel);
|
|||
|
return -EIO;
|
|||
|
}
|
|||
|
return rzg2l_cpg_clk_get_rate_by_name(dev, cc->parent_names[sel - 1]);
|
|||
|
}
|
|||
|
|
|||
|
static ulong rzg2l_div_clk_get_rate(struct udevice *dev, const struct cpg_core_clk *cc)
|
|||
|
{
|
|||
|
struct rzg2l_cpg_data *data =
|
|||
|
(struct rzg2l_cpg_data *)dev_get_driver_data(dev);
|
|||
|
const ulong offset = CPG_CONF_OFFSET(cc->conf);
|
|||
|
const int shift = CPG_CONF_BITPOS(cc->conf);
|
|||
|
const u32 mask = CPG_CONF_BITMASK(cc->conf);
|
|||
|
unsigned int sel, i;
|
|||
|
|
|||
|
sel = (readl(data->base + offset) >> shift) & mask;
|
|||
|
|
|||
|
for (i = 0; cc->dtable[i].div; i++) {
|
|||
|
if (cc->dtable[i].val == sel)
|
|||
|
return rzg2l_cpg_clk_get_rate_by_id(dev, cc->parent) / cc->dtable[i].div;
|
|||
|
}
|
|||
|
dev_err(dev, "Invalid selector value %u for clock %s\n", sel, cc->name);
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
|
|||
|
static ulong rzg2l_core_clk_get_rate(struct udevice *dev, const struct cpg_core_clk *cc)
|
|||
|
{
|
|||
|
switch (cc->type) {
|
|||
|
case CLK_TYPE_FF:
|
|||
|
const ulong parent_rate = rzg2l_cpg_clk_get_rate_by_id(dev, cc->parent);
|
|||
|
return parent_rate * cc->mult / cc->div;
|
|||
|
case CLK_TYPE_IN:
|
|||
|
struct clk clk_in;
|
|||
|
clk_get_by_name(dev, cc->name, &clk_in);
|
|||
|
return clk_get_rate(&clk_in);
|
|||
|
case CLK_TYPE_SD_MUX:
|
|||
|
return rzg2l_sdhi_clk_get_rate(dev, cc);
|
|||
|
case CLK_TYPE_DIV:
|
|||
|
return rzg2l_div_clk_get_rate(dev, cc);
|
|||
|
default:
|
|||
|
dev_err(dev, "get_rate needed for clock %u, type %d\n", cc->id, cc->type);
|
|||
|
return -ENOSYS;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static ulong rzg2l_cpg_clk_get_rate_by_id(struct udevice *dev, unsigned int id)
|
|||
|
{
|
|||
|
struct rzg2l_cpg_data *data =
|
|||
|
(struct rzg2l_cpg_data *)dev_get_driver_data(dev);
|
|||
|
const unsigned int cpg_clk_id = CPG_CLK_ID(id);
|
|||
|
unsigned int i;
|
|||
|
|
|||
|
if (is_mod_clk(id)) {
|
|||
|
for (i = 0; i < data->info->num_mod_clks; i++) {
|
|||
|
if (data->info->mod_clks[i].id == cpg_clk_id)
|
|||
|
return rzg2l_cpg_clk_get_rate_by_id(dev,
|
|||
|
data->info->mod_clks[i].parent);
|
|||
|
}
|
|||
|
|
|||
|
dev_err(dev, "Module clock ID %u not found\n", cpg_clk_id);
|
|||
|
return -ENODEV;
|
|||
|
}
|
|||
|
|
|||
|
for (i = 0; i < data->info->num_core_clks; i++) {
|
|||
|
if (data->info->core_clks[i].id == cpg_clk_id)
|
|||
|
return rzg2l_core_clk_get_rate(dev, &data->info->core_clks[i]);
|
|||
|
}
|
|||
|
|
|||
|
dev_err(dev, "Core clock ID %u not found\n", cpg_clk_id);
|
|||
|
return -ENODEV;
|
|||
|
}
|
|||
|
|
|||
|
static ulong rzg2l_cpg_clk_get_rate_by_name(struct udevice *dev, const char *name)
|
|||
|
{
|
|||
|
struct rzg2l_cpg_data *data =
|
|||
|
(struct rzg2l_cpg_data *)dev_get_driver_data(dev);
|
|||
|
unsigned int i;
|
|||
|
|
|||
|
for (i = 0; i < data->info->num_mod_clks; i++) {
|
|||
|
if (!strcmp(name, data->info->mod_clks[i].name))
|
|||
|
return rzg2l_cpg_clk_get_rate_by_id(dev, data->info->mod_clks[i].parent);
|
|||
|
}
|
|||
|
for (i = 0; i < data->info->num_core_clks; i++) {
|
|||
|
if (!strcmp(name, data->info->core_clks[i].name))
|
|||
|
return rzg2l_core_clk_get_rate(dev, &data->info->core_clks[i]);
|
|||
|
}
|
|||
|
|
|||
|
dev_err(dev, "Clock name %s not found\n", name);
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
|
|||
|
static ulong rzg2l_cpg_clk_get_rate(struct clk *clk)
|
|||
|
{
|
|||
|
return rzg2l_cpg_clk_get_rate_by_id(clk->dev, clk->id);
|
|||
|
}
|
|||
|
|
|||
|
static ulong rzg2l_sdhi_clk_set_rate(struct udevice *dev, const struct cpg_core_clk *cc, ulong rate)
|
|||
|
{
|
|||
|
struct rzg2l_cpg_data *data =
|
|||
|
(struct rzg2l_cpg_data *)dev_get_driver_data(dev);
|
|||
|
const ulong offset = CPG_CONF_OFFSET(cc->conf);
|
|||
|
const int shift = CPG_CONF_BITPOS(cc->conf);
|
|||
|
int channel, new_sel, prev_sel;
|
|||
|
ulong target_rate;
|
|||
|
unsigned int i;
|
|||
|
u32 value;
|
|||
|
|
|||
|
prev_sel = (readl(data->base + offset) >> shift) & 0x3;
|
|||
|
channel = shift / 4;
|
|||
|
|
|||
|
/*
|
|||
|
* Round the requested rate down, unless it is below the minimum
|
|||
|
* supported rate. Assume that the parent clock names are listed in
|
|||
|
* order of descending rate.
|
|||
|
*/
|
|||
|
for (i = 0; i < cc->num_parents; i++) {
|
|||
|
target_rate = rzg2l_cpg_clk_get_rate_by_name(dev, cc->parent_names[i]);
|
|||
|
if (rate >= target_rate) {
|
|||
|
new_sel = i + 1;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (!new_sel)
|
|||
|
new_sel = cc->num_parents - 1;
|
|||
|
|
|||
|
if (new_sel == prev_sel)
|
|||
|
return target_rate;
|
|||
|
dev_dbg(dev, "sdhi set_rate rate=%lu target_rate=%lu sel=%d\n",
|
|||
|
rate, target_rate, new_sel);
|
|||
|
|
|||
|
/*
|
|||
|
* As per the HW manual, we should not directly switch from 533 MHz to
|
|||
|
* 400 MHz and vice versa. To change the setting from 2’b01 (533 MHz)
|
|||
|
* to 2’b10 (400 MHz) or vice versa, Switch to 2’b11 (266 MHz) first,
|
|||
|
* and then switch to the target setting (2’b01 (533 MHz) or 2’b10
|
|||
|
* (400 MHz)).
|
|||
|
*/
|
|||
|
if (new_sel != SEL_SDHI_266MHz && prev_sel != SEL_SDHI_266MHz) {
|
|||
|
u32 waitbit;
|
|||
|
int ret;
|
|||
|
|
|||
|
dev_dbg(dev, "sdhi set_rate via 266MHz\n");
|
|||
|
value = (SEL_SDHI_WRITE_ENABLE | SEL_SDHI_266MHz) << shift;
|
|||
|
writel(value, data->base + offset);
|
|||
|
|
|||
|
/* Wait for the switch to complete. */
|
|||
|
waitbit = channel ? CPG_CLKSTATUS_SELSDHI1_STS : CPG_CLKSTATUS_SELSDHI0_STS;
|
|||
|
ret = readl_poll_timeout(data->base + CPG_CLKSTATUS, value,
|
|||
|
!(value & waitbit),
|
|||
|
CPG_SDHI_CLK_SWITCH_STATUS_TIMEOUT_US);
|
|||
|
if (ret) {
|
|||
|
dev_err(dev, "Failed to switch SDHI%d clock source\n", channel);
|
|||
|
return -EIO;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
value = (SEL_SDHI_WRITE_ENABLE | new_sel) << shift;
|
|||
|
writel(value, data->base + offset);
|
|||
|
|
|||
|
return target_rate;
|
|||
|
}
|
|||
|
|
|||
|
static ulong rzg2l_core_clk_set_rate(struct udevice *dev, const struct cpg_core_clk *cc, ulong rate)
|
|||
|
{
|
|||
|
if (cc->type == CLK_TYPE_SD_MUX)
|
|||
|
return rzg2l_sdhi_clk_set_rate(dev, cc, rate);
|
|||
|
|
|||
|
/*
|
|||
|
* The sdhi driver calls clk_set_rate for SD0_DIV4 and SD1_DIV4, even
|
|||
|
* though they're in a fixed relationship with SD0 and SD1 respectively.
|
|||
|
* To allow the driver to proceed, simply return the current rates
|
|||
|
* without making any change.
|
|||
|
*/
|
|||
|
if (cc->id == CLK_SD0_DIV4 || cc->id == CLK_SD1_DIV4)
|
|||
|
return rzg2l_core_clk_get_rate(dev, cc);
|
|||
|
|
|||
|
dev_err(dev, "set_rate needed for clock %u, type %d\n", cc->id, cc->type);
|
|||
|
return -ENOSYS;
|
|||
|
}
|
|||
|
|
|||
|
static ulong rzg2l_cpg_clk_set_rate_by_id(struct udevice *dev, unsigned int id, ulong rate)
|
|||
|
{
|
|||
|
struct rzg2l_cpg_data *data =
|
|||
|
(struct rzg2l_cpg_data *)dev_get_driver_data(dev);
|
|||
|
const unsigned int cpg_clk_id = CPG_CLK_ID(id);
|
|||
|
unsigned int i;
|
|||
|
|
|||
|
if (is_mod_clk(id)) {
|
|||
|
for (i = 0; i < data->info->num_mod_clks; i++) {
|
|||
|
if (data->info->mod_clks[i].id == cpg_clk_id)
|
|||
|
return rzg2l_cpg_clk_set_rate_by_id(dev,
|
|||
|
data->info->mod_clks[i].parent,
|
|||
|
rate);
|
|||
|
}
|
|||
|
|
|||
|
dev_err(dev, "Module clock ID %u not found\n", cpg_clk_id);
|
|||
|
return -ENODEV;
|
|||
|
}
|
|||
|
|
|||
|
for (i = 0; i < data->info->num_core_clks; i++) {
|
|||
|
if (data->info->core_clks[i].id == cpg_clk_id)
|
|||
|
return rzg2l_core_clk_set_rate(dev, &data->info->core_clks[i], rate);
|
|||
|
}
|
|||
|
|
|||
|
dev_err(dev, "Core clock ID %u not found\n", cpg_clk_id);
|
|||
|
return -ENODEV;
|
|||
|
}
|
|||
|
|
|||
|
static ulong rzg2l_cpg_clk_set_rate(struct clk *clk, ulong rate)
|
|||
|
{
|
|||
|
return rzg2l_cpg_clk_set_rate_by_id(clk->dev, clk->id, rate);
|
|||
|
}
|
|||
|
|
|||
|
static const struct clk_ops rzg2l_cpg_clk_ops = {
|
|||
|
.enable = rzg2l_cpg_clk_enable,
|
|||
|
.disable = rzg2l_cpg_clk_disable,
|
|||
|
.of_xlate = rzg2l_cpg_clk_of_xlate,
|
|||
|
.get_rate = rzg2l_cpg_clk_get_rate,
|
|||
|
.set_rate = rzg2l_cpg_clk_set_rate,
|
|||
|
};
|
|||
|
|
|||
|
U_BOOT_DRIVER(rzg2l_cpg_clk) = {
|
|||
|
.name = "rzg2l-cpg-clk",
|
|||
|
.id = UCLASS_CLK,
|
|||
|
.ops = &rzg2l_cpg_clk_ops,
|
|||
|
.flags = DM_FLAG_VITAL,
|
|||
|
};
|
|||
|
|
|||
|
static int rzg2l_cpg_rst_set(struct reset_ctl *reset_ctl, bool asserted)
|
|||
|
{
|
|||
|
struct rzg2l_cpg_data *data =
|
|||
|
(struct rzg2l_cpg_data *)dev_get_driver_data(reset_ctl->dev);
|
|||
|
const struct rzg2l_reset *rst;
|
|||
|
u32 value;
|
|||
|
|
|||
|
dev_dbg(reset_ctl->dev, "%s %lu\n", asserted ? "assert" : "deassert", reset_ctl->id);
|
|||
|
if (reset_ctl->id >= data->info->num_resets) {
|
|||
|
dev_err(reset_ctl->dev, "Invalid reset id %lu\n", reset_ctl->id);
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
rst = &data->info->resets[reset_ctl->id];
|
|||
|
|
|||
|
value = BIT(rst->bit) << 16;
|
|||
|
if (!asserted)
|
|||
|
value |= BIT(rst->bit);
|
|||
|
writel(value, data->base + rst->off);
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static int rzg2l_cpg_rst_assert(struct reset_ctl *reset_ctl)
|
|||
|
{
|
|||
|
return rzg2l_cpg_rst_set(reset_ctl, true);
|
|||
|
}
|
|||
|
|
|||
|
static int rzg2l_cpg_rst_deassert(struct reset_ctl *reset_ctl)
|
|||
|
{
|
|||
|
return rzg2l_cpg_rst_set(reset_ctl, false);
|
|||
|
}
|
|||
|
|
|||
|
static int rzg2l_cpg_rst_of_xlate(struct reset_ctl *reset_ctl,
|
|||
|
struct ofnode_phandle_args *args)
|
|||
|
{
|
|||
|
struct rzg2l_cpg_data *data =
|
|||
|
(struct rzg2l_cpg_data *)dev_get_driver_data(reset_ctl->dev);
|
|||
|
|
|||
|
if (args->args[0] >= data->info->num_resets)
|
|||
|
return -EINVAL;
|
|||
|
|
|||
|
reset_ctl->id = args->args[0];
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static const struct reset_ops rzg2l_cpg_rst_ops = {
|
|||
|
.rst_assert = rzg2l_cpg_rst_assert,
|
|||
|
.rst_deassert = rzg2l_cpg_rst_deassert,
|
|||
|
.of_xlate = rzg2l_cpg_rst_of_xlate,
|
|||
|
};
|
|||
|
|
|||
|
U_BOOT_DRIVER(rzg2l_cpg_rst) = {
|
|||
|
.name = "rzg2l-cpg-rst",
|
|||
|
.id = UCLASS_RESET,
|
|||
|
.ops = &rzg2l_cpg_rst_ops,
|
|||
|
.flags = DM_FLAG_VITAL,
|
|||
|
};
|
|||
|
|
|||
|
int rzg2l_cpg_bind(struct udevice *parent)
|
|||
|
{
|
|||
|
struct udevice *cdev, *rdev;
|
|||
|
struct rzg2l_cpg_data *data;
|
|||
|
struct driver *drv;
|
|||
|
int ret;
|
|||
|
|
|||
|
data = devm_kmalloc(parent, sizeof(*data), 0);
|
|||
|
if (!data)
|
|||
|
return -ENOMEM;
|
|||
|
|
|||
|
data->base = dev_read_addr_ptr(parent);
|
|||
|
if (!data->base)
|
|||
|
return -EINVAL;
|
|||
|
|
|||
|
data->info = (struct rzg2l_cpg_info *)dev_get_driver_data(parent);
|
|||
|
if (!data->info)
|
|||
|
return -EINVAL;
|
|||
|
|
|||
|
drv = lists_driver_lookup_name("rzg2l-cpg-clk");
|
|||
|
if (!drv)
|
|||
|
return -ENOENT;
|
|||
|
|
|||
|
ret = device_bind_with_driver_data(parent, drv, parent->name,
|
|||
|
(ulong)data, dev_ofnode(parent),
|
|||
|
&cdev);
|
|||
|
if (ret)
|
|||
|
return ret;
|
|||
|
|
|||
|
drv = lists_driver_lookup_name("rzg2l-cpg-rst");
|
|||
|
if (!drv) {
|
|||
|
device_unbind(cdev);
|
|||
|
return -ENOENT;
|
|||
|
}
|
|||
|
|
|||
|
ret = device_bind_with_driver_data(parent, drv, parent->name,
|
|||
|
(ulong)data, dev_ofnode(parent),
|
|||
|
&rdev);
|
|||
|
if (ret)
|
|||
|
device_unbind(cdev);
|
|||
|
|
|||
|
return ret;
|
|||
|
}
|