2019-09-28 00:14:26 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
/*
|
|
|
|
* Texas Instruments CDCE913/925/937/949 clock synthesizer driver
|
|
|
|
*
|
|
|
|
* Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
|
|
|
|
* Tero Kristo <t-kristo@ti.com>
|
|
|
|
*
|
|
|
|
* Based on Linux kernel clk-cdce925.c.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <common.h>
|
|
|
|
#include <dm.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <clk-uclass.h>
|
|
|
|
#include <i2c.h>
|
2020-02-03 22:36:16 +08:00
|
|
|
#include <dm/device_compat.h>
|
2020-05-11 01:40:13 +08:00
|
|
|
#include <linux/bitops.h>
|
2019-09-28 00:14:26 +08:00
|
|
|
|
|
|
|
#define MAX_NUMBER_OF_PLLS 4
|
|
|
|
#define MAX_NUMER_OF_OUTPUTS 9
|
|
|
|
|
|
|
|
#define CDCE9XX_REG_GLOBAL1 0x01
|
|
|
|
#define CDCE9XX_REG_Y1SPIPDIVH 0x02
|
|
|
|
#define CDCE9XX_REG_PDIV1L 0x03
|
|
|
|
#define CDCE9XX_REG_XCSEL 0x05
|
|
|
|
|
|
|
|
#define CDCE9XX_PDIV1_H_MASK 0x3
|
|
|
|
|
|
|
|
#define CDCE9XX_REG_PDIV(clk) (0x16 + (((clk) - 1) & 1) + \
|
|
|
|
((clk) - 1) / 2 * 0x10)
|
|
|
|
|
|
|
|
#define CDCE9XX_PDIV_MASK 0x7f
|
|
|
|
|
|
|
|
#define CDCE9XX_BYTE_TRANSFER BIT(7)
|
|
|
|
|
|
|
|
struct cdce9xx_chip_info {
|
|
|
|
int num_plls;
|
|
|
|
int num_outputs;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct cdce9xx_clk_data {
|
|
|
|
struct udevice *i2c;
|
|
|
|
struct cdce9xx_chip_info *chip;
|
|
|
|
u32 xtal_rate;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct cdce9xx_chip_info cdce913_chip_info = {
|
|
|
|
.num_plls = 1, .num_outputs = 3,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct cdce9xx_chip_info cdce925_chip_info = {
|
|
|
|
.num_plls = 2, .num_outputs = 5,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct cdce9xx_chip_info cdce937_chip_info = {
|
|
|
|
.num_plls = 3, .num_outputs = 7,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct cdce9xx_chip_info cdce949_chip_info = {
|
|
|
|
.num_plls = 4, .num_outputs = 9,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int cdce9xx_reg_read(struct udevice *dev, u8 addr, u8 *buf)
|
|
|
|
{
|
|
|
|
struct cdce9xx_clk_data *data = dev_get_priv(dev);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = dm_i2c_read(data->i2c, addr | CDCE9XX_BYTE_TRANSFER, buf, 1);
|
|
|
|
if (ret)
|
|
|
|
dev_err(dev, "%s: failed for addr:%x, ret:%d\n", __func__,
|
|
|
|
addr, ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cdce9xx_reg_write(struct udevice *dev, u8 addr, u8 val)
|
|
|
|
{
|
|
|
|
struct cdce9xx_clk_data *data = dev_get_priv(dev);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = dm_i2c_write(data->i2c, addr | CDCE9XX_BYTE_TRANSFER, &val, 1);
|
|
|
|
if (ret)
|
|
|
|
dev_err(dev, "%s: failed for addr:%x, ret:%d\n", __func__,
|
|
|
|
addr, ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cdce9xx_clk_of_xlate(struct clk *clk,
|
|
|
|
struct ofnode_phandle_args *args)
|
|
|
|
{
|
|
|
|
struct cdce9xx_clk_data *data = dev_get_priv(clk->dev);
|
|
|
|
|
|
|
|
if (args->args_count != 1)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (args->args[0] > data->chip->num_outputs)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
clk->id = args->args[0];
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cdce9xx_clk_probe(struct udevice *dev)
|
|
|
|
{
|
|
|
|
struct cdce9xx_clk_data *data = dev_get_priv(dev);
|
|
|
|
struct cdce9xx_chip_info *chip = (void *)dev_get_driver_data(dev);
|
|
|
|
int ret;
|
|
|
|
u32 val;
|
|
|
|
struct clk clk;
|
|
|
|
|
|
|
|
val = (u32)dev_read_addr_ptr(dev);
|
|
|
|
|
|
|
|
ret = i2c_get_chip(dev->parent, val, 1, &data->i2c);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "I2C probe failed.\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
data->chip = chip;
|
|
|
|
|
|
|
|
ret = clk_get_by_index(dev, 0, &clk);
|
|
|
|
data->xtal_rate = clk_get_rate(&clk);
|
|
|
|
|
|
|
|
val = dev_read_u32_default(dev, "xtal-load-pf", -1);
|
|
|
|
if (val >= 0)
|
|
|
|
cdce9xx_reg_write(dev, CDCE9XX_REG_XCSEL, val << 3);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static u16 cdce9xx_clk_get_pdiv(struct clk *clk)
|
|
|
|
{
|
|
|
|
u8 val;
|
|
|
|
u16 pdiv;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (clk->id == 0) {
|
|
|
|
ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_Y1SPIPDIVH, &val);
|
|
|
|
if (ret)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pdiv = (val & CDCE9XX_PDIV1_H_MASK) << 8;
|
|
|
|
|
|
|
|
ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_PDIV1L, &val);
|
|
|
|
if (ret)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pdiv |= val;
|
|
|
|
} else {
|
|
|
|
ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_PDIV(clk->id),
|
|
|
|
&val);
|
|
|
|
if (ret)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pdiv = val & CDCE9XX_PDIV_MASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pdiv;
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 cdce9xx_clk_get_parent_rate(struct clk *clk)
|
|
|
|
{
|
|
|
|
struct cdce9xx_clk_data *data = dev_get_priv(clk->dev);
|
|
|
|
|
|
|
|
return data->xtal_rate;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ulong cdce9xx_clk_get_rate(struct clk *clk)
|
|
|
|
{
|
|
|
|
u32 parent_rate;
|
|
|
|
u16 pdiv;
|
|
|
|
|
|
|
|
parent_rate = cdce9xx_clk_get_parent_rate(clk);
|
|
|
|
|
|
|
|
pdiv = cdce9xx_clk_get_pdiv(clk);
|
|
|
|
|
|
|
|
return parent_rate / pdiv;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ulong cdce9xx_clk_set_rate(struct clk *clk, ulong rate)
|
|
|
|
{
|
|
|
|
u32 parent_rate;
|
|
|
|
int pdiv;
|
|
|
|
u32 diff;
|
|
|
|
u8 val;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
parent_rate = cdce9xx_clk_get_parent_rate(clk);
|
|
|
|
|
|
|
|
pdiv = parent_rate / rate;
|
|
|
|
|
|
|
|
diff = rate - parent_rate / pdiv;
|
|
|
|
|
|
|
|
if (rate - parent_rate / (pdiv + 1) < diff)
|
|
|
|
pdiv++;
|
|
|
|
|
|
|
|
if (clk->id == 0) {
|
|
|
|
ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_Y1SPIPDIVH, &val);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
val &= ~CDCE9XX_PDIV1_H_MASK;
|
|
|
|
|
|
|
|
val |= (pdiv >> 8);
|
|
|
|
|
|
|
|
ret = cdce9xx_reg_write(clk->dev, CDCE9XX_REG_Y1SPIPDIVH, val);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = cdce9xx_reg_write(clk->dev, CDCE9XX_REG_PDIV1L,
|
|
|
|
(pdiv & 0xff));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
} else {
|
|
|
|
ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_PDIV(clk->id),
|
|
|
|
&val);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
val &= ~CDCE9XX_PDIV_MASK;
|
|
|
|
|
|
|
|
val |= pdiv;
|
|
|
|
|
|
|
|
ret = cdce9xx_reg_write(clk->dev, CDCE9XX_REG_PDIV(clk->id),
|
|
|
|
val);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct udevice_id cdce9xx_clk_of_match[] = {
|
|
|
|
{ .compatible = "ti,cdce913", .data = (u32)&cdce913_chip_info },
|
|
|
|
{ .compatible = "ti,cdce925", .data = (u32)&cdce925_chip_info },
|
|
|
|
{ .compatible = "ti,cdce937", .data = (u32)&cdce937_chip_info },
|
|
|
|
{ .compatible = "ti,cdce949", .data = (u32)&cdce949_chip_info },
|
|
|
|
{ /* sentinel */ },
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct clk_ops cdce9xx_clk_ops = {
|
|
|
|
.of_xlate = cdce9xx_clk_of_xlate,
|
|
|
|
.get_rate = cdce9xx_clk_get_rate,
|
|
|
|
.set_rate = cdce9xx_clk_set_rate,
|
|
|
|
};
|
|
|
|
|
|
|
|
U_BOOT_DRIVER(cdce9xx_clk) = {
|
|
|
|
.name = "cdce9xx-clk",
|
|
|
|
.id = UCLASS_CLK,
|
|
|
|
.of_match = cdce9xx_clk_of_match,
|
|
|
|
.probe = cdce9xx_clk_probe,
|
|
|
|
.priv_auto_alloc_size = sizeof(struct cdce9xx_clk_data),
|
|
|
|
.ops = &cdce9xx_clk_ops,
|
|
|
|
};
|