// SPDX-License-Identifier: GPL-2.0+ /* * Texas Instruments CDCE913/925/937/949 clock synthesizer driver * * Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com/ * Tero Kristo * * Based on Linux kernel clk-cdce925.c. */ #include #include #include #include #include #include #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_request(struct clk *clk) { struct cdce9xx_clk_data *data = dev_get_priv(clk->dev); if (clk->id > data->chip->num_outputs) return -EINVAL; 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 = { .request = cdce9xx_clk_request, .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 = sizeof(struct cdce9xx_clk_data), .ops = &cdce9xx_clk_ops, };