531 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Driver for Silicon Labs Si570/Si571 Programmable XO/VCXO4 *5 * Copyright (C) 2010, 2011 Ericsson AB.6 * Copyright (C) 2011 Guenter Roeck.7 * Copyright (C) 2011 - 2021 Xilinx Inc.8 *9 * Author: Guenter Roeck <guenter.roeck@ericsson.com>10 * Sören Brinkmann <soren.brinkmann@xilinx.com>11 */12 13#include <linux/clk.h>14#include <linux/clk-provider.h>15#include <linux/delay.h>16#include <linux/module.h>17#include <linux/i2c.h>18#include <linux/regmap.h>19#include <linux/slab.h>20 21/* Si570 registers */22#define SI570_REG_HS_N1 723#define SI570_REG_N1_RFREQ0 824#define SI570_REG_RFREQ1 925#define SI570_REG_RFREQ2 1026#define SI570_REG_RFREQ3 1127#define SI570_REG_RFREQ4 1228#define SI570_REG_CONTROL 13529#define SI570_REG_FREEZE_DCO 13730#define SI570_DIV_OFFSET_7PPM 631 32#define HS_DIV_SHIFT 533#define HS_DIV_MASK 0xe034#define HS_DIV_OFFSET 435#define N1_6_2_MASK 0x1f36#define N1_1_0_MASK 0xc037#define RFREQ_37_32_MASK 0x3f38 39#define SI570_MIN_FREQ 10000000L40#define SI570_MAX_FREQ 1417500000L41#define SI598_MAX_FREQ 525000000L42 43#define FDCO_MIN 4850000000LL44#define FDCO_MAX 5670000000LL45 46#define SI570_CNTRL_RECALL (1 << 0)47#define SI570_CNTRL_FREEZE_M (1 << 5)48#define SI570_CNTRL_NEWFREQ (1 << 6)49 50#define SI570_FREEZE_DCO (1 << 4)51 52/**53 * struct clk_si570_info:54 * @max_freq: Maximum frequency for this device55 * @has_temperature_stability: Device support temperature stability56 */57struct clk_si570_info {58 u64 max_freq;59 bool has_temperature_stability;60};61 62/**63 * struct clk_si570:64 * @hw: Clock hw struct65 * @regmap: Device's regmap66 * @div_offset: Rgister offset for dividers67 * @info: Device info68 * @fxtal: Factory xtal frequency69 * @n1: Clock divider N170 * @hs_div: Clock divider HSDIV71 * @rfreq: Clock multiplier RFREQ72 * @frequency: Current output frequency73 * @i2c_client: I2C client pointer74 */75struct clk_si570 {76 struct clk_hw hw;77 struct regmap *regmap;78 unsigned int div_offset;79 const struct clk_si570_info *info;80 u64 fxtal;81 unsigned int n1;82 unsigned int hs_div;83 u64 rfreq;84 u64 frequency;85 struct i2c_client *i2c_client;86};87#define to_clk_si570(_hw) container_of(_hw, struct clk_si570, hw)88 89/**90 * si570_get_divs() - Read clock dividers from HW91 * @data: Pointer to struct clk_si57092 * @rfreq: Fractional multiplier (output)93 * @n1: Divider N1 (output)94 * @hs_div: Divider HSDIV (output)95 * Returns 0 on success, negative errno otherwise.96 *97 * Retrieve clock dividers and multipliers from the HW.98 */99static int si570_get_divs(struct clk_si570 *data, u64 *rfreq,100 unsigned int *n1, unsigned int *hs_div)101{102 int err;103 u8 reg[6];104 u64 tmp;105 106 err = regmap_bulk_read(data->regmap, SI570_REG_HS_N1 + data->div_offset,107 reg, ARRAY_SIZE(reg));108 if (err)109 return err;110 111 *hs_div = ((reg[0] & HS_DIV_MASK) >> HS_DIV_SHIFT) + HS_DIV_OFFSET;112 *n1 = ((reg[0] & N1_6_2_MASK) << 2) + ((reg[1] & N1_1_0_MASK) >> 6) + 1;113 /* Handle invalid cases */114 if (*n1 > 1)115 *n1 &= ~1;116 117 tmp = reg[1] & RFREQ_37_32_MASK;118 tmp = (tmp << 8) + reg[2];119 tmp = (tmp << 8) + reg[3];120 tmp = (tmp << 8) + reg[4];121 tmp = (tmp << 8) + reg[5];122 *rfreq = tmp;123 124 return 0;125}126 127/**128 * si570_get_defaults() - Get default values129 * @data: Driver data structure130 * @fout: Factory frequency output131 * @skip_recall: If true, don't recall NVM into RAM132 * Returns 0 on success, negative errno otherwise.133 */134static int si570_get_defaults(struct clk_si570 *data, u64 fout,135 bool skip_recall)136{137 int err;138 u64 fdco;139 140 if (!skip_recall)141 regmap_write(data->regmap, SI570_REG_CONTROL,142 SI570_CNTRL_RECALL);143 144 err = si570_get_divs(data, &data->rfreq, &data->n1, &data->hs_div);145 if (err)146 return err;147 148 /*149 * Accept optional precision loss to avoid arithmetic overflows.150 * Acceptable per Silicon Labs Application Note AN334.151 */152 fdco = fout * data->n1 * data->hs_div;153 if (fdco >= (1LL << 36))154 data->fxtal = div64_u64(fdco << 24, data->rfreq >> 4);155 else156 data->fxtal = div64_u64(fdco << 28, data->rfreq);157 158 data->frequency = fout;159 160 return 0;161}162 163/**164 * si570_update_rfreq() - Update clock multiplier165 * @data: Driver data structure166 * Passes on regmap_bulk_write() return value.167 */168static int si570_update_rfreq(struct clk_si570 *data)169{170 u8 reg[5];171 172 reg[0] = ((data->n1 - 1) << 6) |173 ((data->rfreq >> 32) & RFREQ_37_32_MASK);174 reg[1] = (data->rfreq >> 24) & 0xff;175 reg[2] = (data->rfreq >> 16) & 0xff;176 reg[3] = (data->rfreq >> 8) & 0xff;177 reg[4] = data->rfreq & 0xff;178 179 return regmap_bulk_write(data->regmap, SI570_REG_N1_RFREQ0 +180 data->div_offset, reg, ARRAY_SIZE(reg));181}182 183/**184 * si570_calc_divs() - Caluclate clock dividers185 * @frequency: Target frequency186 * @data: Driver data structure187 * @out_rfreq: RFREG fractional multiplier (output)188 * @out_n1: Clock divider N1 (output)189 * @out_hs_div: Clock divider HSDIV (output)190 * Returns 0 on success, negative errno otherwise.191 *192 * Calculate the clock dividers (@out_hs_div, @out_n1) and clock multiplier193 * (@out_rfreq) for a given target @frequency.194 */195static int si570_calc_divs(unsigned long frequency, struct clk_si570 *data,196 u64 *out_rfreq, unsigned int *out_n1, unsigned int *out_hs_div)197{198 int i;199 unsigned int n1, hs_div;200 u64 fdco, best_fdco = ULLONG_MAX;201 static const uint8_t si570_hs_div_values[] = { 11, 9, 7, 6, 5, 4 };202 203 for (i = 0; i < ARRAY_SIZE(si570_hs_div_values); i++) {204 hs_div = si570_hs_div_values[i];205 /* Calculate lowest possible value for n1 */206 n1 = div_u64(div_u64(FDCO_MIN, hs_div), frequency);207 if (!n1 || (n1 & 1))208 n1++;209 while (n1 <= 128) {210 fdco = (u64)frequency * (u64)hs_div * (u64)n1;211 if (fdco > FDCO_MAX)212 break;213 if (fdco >= FDCO_MIN && fdco < best_fdco) {214 *out_n1 = n1;215 *out_hs_div = hs_div;216 *out_rfreq = div64_u64(fdco << 28, data->fxtal);217 best_fdco = fdco;218 }219 n1 += (n1 == 1 ? 1 : 2);220 }221 }222 223 if (best_fdco == ULLONG_MAX)224 return -EINVAL;225 226 return 0;227}228 229static unsigned long si570_recalc_rate(struct clk_hw *hw,230 unsigned long parent_rate)231{232 int err;233 u64 rfreq, rate;234 unsigned int n1, hs_div;235 struct clk_si570 *data = to_clk_si570(hw);236 237 err = si570_get_divs(data, &rfreq, &n1, &hs_div);238 if (err) {239 dev_err(&data->i2c_client->dev, "unable to recalc rate\n");240 return data->frequency;241 }242 243 rfreq = div_u64(rfreq, hs_div * n1);244 rate = (data->fxtal * rfreq) >> 28;245 246 return rate;247}248 249static long si570_round_rate(struct clk_hw *hw, unsigned long rate,250 unsigned long *parent_rate)251{252 int err;253 u64 rfreq;254 unsigned int n1, hs_div;255 struct clk_si570 *data = to_clk_si570(hw);256 257 if (!rate)258 return 0;259 260 if (div64_u64(abs(rate - data->frequency) * 10000LL,261 data->frequency) < 35) {262 rfreq = div64_u64((data->rfreq * rate) +263 div64_u64(data->frequency, 2), data->frequency);264 n1 = data->n1;265 hs_div = data->hs_div;266 267 } else {268 err = si570_calc_divs(rate, data, &rfreq, &n1, &hs_div);269 if (err) {270 dev_err(&data->i2c_client->dev,271 "unable to round rate\n");272 return 0;273 }274 }275 276 return rate;277}278 279/**280 * si570_set_frequency() - Adjust output frequency281 * @data: Driver data structure282 * @frequency: Target frequency283 * Returns 0 on success.284 *285 * Update output frequency for big frequency changes (> 3,500 ppm).286 */287static int si570_set_frequency(struct clk_si570 *data, unsigned long frequency)288{289 int err;290 291 err = si570_calc_divs(frequency, data, &data->rfreq, &data->n1,292 &data->hs_div);293 if (err)294 return err;295 296 /*297 * The DCO reg should be accessed with a read-modify-write operation298 * per AN334299 */300 regmap_write(data->regmap, SI570_REG_FREEZE_DCO, SI570_FREEZE_DCO);301 regmap_write(data->regmap, SI570_REG_HS_N1 + data->div_offset,302 ((data->hs_div - HS_DIV_OFFSET) << HS_DIV_SHIFT) |303 (((data->n1 - 1) >> 2) & N1_6_2_MASK));304 si570_update_rfreq(data);305 regmap_write(data->regmap, SI570_REG_FREEZE_DCO, 0);306 regmap_write(data->regmap, SI570_REG_CONTROL, SI570_CNTRL_NEWFREQ);307 308 /* Applying a new frequency can take up to 10ms */309 usleep_range(10000, 12000);310 311 return 0;312}313 314/**315 * si570_set_frequency_small() - Adjust output frequency316 * @data: Driver data structure317 * @frequency: Target frequency318 * Returns 0 on success.319 *320 * Update output frequency for small frequency changes (< 3,500 ppm).321 */322static int si570_set_frequency_small(struct clk_si570 *data,323 unsigned long frequency)324{325 /*326 * This is a re-implementation of DIV_ROUND_CLOSEST327 * using the div64_u64 function lieu of letting the compiler328 * insert EABI calls329 */330 data->rfreq = div64_u64((data->rfreq * frequency) +331 div_u64(data->frequency, 2), data->frequency);332 regmap_write(data->regmap, SI570_REG_CONTROL, SI570_CNTRL_FREEZE_M);333 si570_update_rfreq(data);334 regmap_write(data->regmap, SI570_REG_CONTROL, 0);335 336 /* Applying a new frequency (small change) can take up to 100us */337 usleep_range(100, 200);338 339 return 0;340}341 342static int si570_set_rate(struct clk_hw *hw, unsigned long rate,343 unsigned long parent_rate)344{345 struct clk_si570 *data = to_clk_si570(hw);346 struct i2c_client *client = data->i2c_client;347 int err;348 349 if (rate < SI570_MIN_FREQ || rate > data->info->max_freq) {350 dev_err(&client->dev,351 "requested frequency %lu Hz is out of range\n", rate);352 return -EINVAL;353 }354 355 if (div64_u64(abs(rate - data->frequency) * 10000LL,356 data->frequency) < 35)357 err = si570_set_frequency_small(data, rate);358 else359 err = si570_set_frequency(data, rate);360 361 if (err)362 return err;363 364 data->frequency = rate;365 366 return 0;367}368 369static const struct clk_ops si570_clk_ops = {370 .recalc_rate = si570_recalc_rate,371 .round_rate = si570_round_rate,372 .set_rate = si570_set_rate,373};374 375static bool si570_regmap_is_volatile(struct device *dev, unsigned int reg)376{377 switch (reg) {378 case SI570_REG_CONTROL:379 return true;380 default:381 return false;382 }383}384 385static bool si570_regmap_is_writeable(struct device *dev, unsigned int reg)386{387 switch (reg) {388 case SI570_REG_HS_N1 ... (SI570_REG_RFREQ4 + SI570_DIV_OFFSET_7PPM):389 case SI570_REG_CONTROL:390 case SI570_REG_FREEZE_DCO:391 return true;392 default:393 return false;394 }395}396 397static const struct regmap_config si570_regmap_config = {398 .reg_bits = 8,399 .val_bits = 8,400 .cache_type = REGCACHE_MAPLE,401 .max_register = 137,402 .writeable_reg = si570_regmap_is_writeable,403 .volatile_reg = si570_regmap_is_volatile,404};405 406static int si570_probe(struct i2c_client *client)407{408 struct clk_si570 *data;409 struct clk_init_data init;410 u32 initial_fout, factory_fout, stability;411 bool skip_recall;412 int err;413 414 data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);415 if (!data)416 return -ENOMEM;417 418 init.ops = &si570_clk_ops;419 init.flags = 0;420 init.num_parents = 0;421 data->hw.init = &init;422 data->i2c_client = client;423 424 data->info = i2c_get_match_data(client);425 if (data->info->has_temperature_stability) {426 err = of_property_read_u32(client->dev.of_node,427 "temperature-stability", &stability);428 if (err) {429 dev_err(&client->dev,430 "'temperature-stability' property missing\n");431 return err;432 }433 /* adjust register offsets for 7ppm devices */434 if (stability == 7)435 data->div_offset = SI570_DIV_OFFSET_7PPM;436 }437 438 if (of_property_read_string(client->dev.of_node, "clock-output-names",439 &init.name))440 init.name = client->dev.of_node->name;441 442 err = of_property_read_u32(client->dev.of_node, "factory-fout",443 &factory_fout);444 if (err) {445 dev_err(&client->dev, "'factory-fout' property missing\n");446 return err;447 }448 449 skip_recall = of_property_read_bool(client->dev.of_node,450 "silabs,skip-recall");451 452 data->regmap = devm_regmap_init_i2c(client, &si570_regmap_config);453 if (IS_ERR(data->regmap)) {454 dev_err(&client->dev, "failed to allocate register map\n");455 return PTR_ERR(data->regmap);456 }457 458 i2c_set_clientdata(client, data);459 err = si570_get_defaults(data, factory_fout, skip_recall);460 if (err)461 return err;462 463 err = devm_clk_hw_register(&client->dev, &data->hw);464 if (err) {465 dev_err(&client->dev, "clock registration failed\n");466 return err;467 }468 err = devm_of_clk_add_hw_provider(&client->dev, of_clk_hw_simple_get,469 &data->hw);470 if (err) {471 dev_err(&client->dev, "unable to add clk provider\n");472 return err;473 }474 475 /* Read the requested initial output frequency from device tree */476 if (!of_property_read_u32(client->dev.of_node, "clock-frequency",477 &initial_fout)) {478 err = clk_set_rate(data->hw.clk, initial_fout);479 if (err)480 return err;481 }482 483 /* Display a message indicating that we've successfully registered */484 dev_info(&client->dev, "registered, current frequency %llu Hz\n",485 data->frequency);486 487 return 0;488}489 490static const struct clk_si570_info clk_si570_info = {491 .max_freq = SI570_MAX_FREQ,492 .has_temperature_stability = true,493};494 495static const struct clk_si570_info clk_si590_info = {496 .max_freq = SI598_MAX_FREQ,497};498 499static const struct i2c_device_id si570_id[] = {500 { "si570", (kernel_ulong_t)&clk_si570_info },501 { "si571", (kernel_ulong_t)&clk_si570_info },502 { "si598", (kernel_ulong_t)&clk_si590_info },503 { "si599", (kernel_ulong_t)&clk_si590_info },504 { }505};506MODULE_DEVICE_TABLE(i2c, si570_id);507 508static const struct of_device_id clk_si570_of_match[] = {509 { .compatible = "silabs,si570", .data = &clk_si570_info },510 { .compatible = "silabs,si571", .data = &clk_si570_info },511 { .compatible = "silabs,si598", .data = &clk_si590_info },512 { .compatible = "silabs,si599", .data = &clk_si590_info },513 { }514};515MODULE_DEVICE_TABLE(of, clk_si570_of_match);516 517static struct i2c_driver si570_driver = {518 .driver = {519 .name = "si570",520 .of_match_table = clk_si570_of_match,521 },522 .probe = si570_probe,523 .id_table = si570_id,524};525module_i2c_driver(si570_driver);526 527MODULE_AUTHOR("Guenter Roeck <guenter.roeck@ericsson.com>");528MODULE_AUTHOR("Soeren Brinkmann <soren.brinkmann@xilinx.com>");529MODULE_DESCRIPTION("Si570 driver");530MODULE_LICENSE("GPL");531