diff options
Diffstat (limited to 'drivers/clk/idt/clk-idt8t49n24x-core.c')
-rw-r--r-- | drivers/clk/idt/clk-idt8t49n24x-core.c | 933 |
1 files changed, 933 insertions, 0 deletions
diff --git a/drivers/clk/idt/clk-idt8t49n24x-core.c b/drivers/clk/idt/clk-idt8t49n24x-core.c new file mode 100644 index 000000000000..ad23014e708f --- /dev/null +++ b/drivers/clk/idt/clk-idt8t49n24x-core.c @@ -0,0 +1,933 @@ +// SPDX-License-Identifier: GPL-2.0 +/* clk-idt8t49n24x-core.c - Program 8T49N24x settings via I2C (common code) + * + * Copyright (C) 2018, Integrated Device Technology, Inc. <david.cater@idt.com> + * + * See https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html + * This program is distributed "AS IS" and WITHOUT ANY WARRANTY; + * including the implied warranties of MERCHANTABILITY, FITNESS FOR + * A PARTICULAR PURPOSE, or NON-INFRINGEMENT. + */ + +#include <linux/i2c.h> +#include <linux/regmap.h> + +#include "clk-idt8t49n24x-core.h" + +/* + * In Timing Commander, Q0 is changed from 25MHz to Q0 75MHz, the following + * changes occur: + * + * 2 bytes change in EEPROM data string. + * + * DSM_INT R0025[0],R0026[7:0] : 35 => 30 + * NS2_Q0 R0040[7:0],R0041[7:0] : 14 => 4 + * + * In EEPROM + * 1. R0026 + * 2. R0041 + * + * Note that VCO_Frequency (metadata) also changed (3500 =>3000). + * This reflects a change to DSM_INT. + * + * Note that the Timing Commander code has workarounds in the workflow scripts + * to handle dividers for the 8T49N241 (because the development of that GUI + * predates chip override functionality). That affects NS1_Qx (x in 1-3) + * and NS2_Qx. NS1_Qx contains the upper bits of NS_Qx, and NS2_Qx contains + * the lower bits. That is NOT the case for Q0, though. In that case NS1_Q0 + * is the 1st stage output divider (/5, /6, /4) and NS2_Q0 is the 16-bit + * second stage (with actual divide being twice the value stored in the + * register). + * + * NS1_Q0 R003F[1:0] + */ + +#define IDT24x_VCO_MIN 2999997000u +#define IDT24x_VCO_MAX 4000004000u +#define IDT24x_VCO_OPT 3500000000u +#define IDT24x_MIN_INT_DIVIDER 6 +#define IDT24x_MIN_NS1 4 +#define IDT24x_MAX_NS1 6 + +static u8 q0_ns1_options[3] = { 5, 6, 4 }; + +/** + * bits_to_shift - num bits to shift given specified mask + * @mask: 32-bit word input to count zero bits on right + * + * Given a bit mask indicating where a value will be stored in + * a register, return the number of bits you need to shift the value + * before ORing it into the register value. + * + * Return: number of bits to shift + */ +int bits_to_shift(unsigned int mask) +{ + /* the number of zero bits on the right */ + unsigned int c = 32; + + mask &= ~mask + 1; + if (mask) + c--; + if (mask & 0x0000FFFF) + c -= 16; + if (mask & 0x00FF00FF) + c -= 8; + if (mask & 0x0F0F0F0F) + c -= 4; + if (mask & 0x33333333) + c -= 2; + if (mask & 0x55555555) + c -= 1; + return c; +} + +/* + * TODO: Consider replacing this with regmap_multi_reg_write, which + * supports introducing a delay after each write. Experiment to see if + * the writes succeed consistently when using that API. + */ +static int regmap_bulk_write_with_retry( + struct regmap *map, unsigned int offset, u8 val[], + int val_count, int max_attempts) +{ + int err = 0; + int count = 1; + + do { + err = regmap_bulk_write(map, offset, val, val_count); + if (err == 0) + return 0; + + usleep_range(100, 200); + } while (count++ <= max_attempts); + return err; +} + +static int regmap_write_with_retry( + struct regmap *map, unsigned int offset, unsigned int val, + int max_attempts) +{ + int err = 0; + int count = 1; + + do { + err = regmap_write(map, offset, val); + if (err == 0) + return 0; + usleep_range(100, 200); + } while (count++ <= max_attempts); + return err; +} + +/* + * TODO: Consider using regmap_multi_reg_write instead. Explore + * use of regmap to configure WRITE_BLOCK_SIZE, and using the delay + * mechanism in regmap_multi_reg_write instead of retrying multiple + * times (regmap_bulk_write_with_retry). + */ +int i2cwritebulk( + struct i2c_client *client, struct regmap *map, + unsigned int reg, u8 val[], size_t val_count) +{ + char dbg[128]; + u8 block[WRITE_BLOCK_SIZE]; + unsigned int block_offset = reg; + int x; + int err = 0; + int currentOffset = 0; + + dev_dbg(&client->dev, "I2C->0x%04x : [hex] . First byte: %02x, Second byte: %02x", + reg, reg >> 8, reg & 0xFF); + dbg[0] = 0; + + for (x = 0; x < val_count; x++) { + char data[4]; + + block[currentOffset++] = val[x]; + sprintf(data, "%02x ", val[x]); + strcat(dbg, data); + if (x > 0 && (x + 1) % WRITE_BLOCK_SIZE == 0) { + dev_dbg(&client->dev, "%s", dbg); + dbg[0] = '\0'; + sprintf(dbg, + "(loop) calling regmap_bulk_write @ 0x%04x [%d bytes]", + block_offset, WRITE_BLOCK_SIZE); + dev_dbg(&client->dev, "%s", dbg); + dbg[0] = '\0'; + err = regmap_bulk_write_with_retry( + map, block_offset, block, WRITE_BLOCK_SIZE, 5); + if (err != 0) + break; + block_offset += WRITE_BLOCK_SIZE; + currentOffset = 0; + } + } + if (err == 0 && currentOffset > 0) { + dev_dbg(&client->dev, "%s", dbg); + dev_dbg(&client->dev, "(final) calling regmap_bulk_write @ 0x%04x [%d bytes]", + block_offset, currentOffset); + err = regmap_bulk_write_with_retry( + map, block_offset, block, currentOffset, 5); + } + + return err; +} + +static int i2cwrite( + struct i2c_client *client, struct regmap *map, + unsigned int reg, unsigned int val) +{ + int err; + + dev_dbg(&client->dev, "I2C->0x%x : [hex] %x", reg, val); + err = regmap_write_with_retry(map, reg, val, 5); + usleep_range(100, 200); + return err; +} + +static int i2cwritewithmask( + struct i2c_client *client, struct regmap *map, unsigned int reg, + u8 val, u8 original, u8 mask) +{ + return i2cwrite(client, map, reg, + ((val << bits_to_shift(mask)) & mask) | (original & ~mask)); +} + +int idt24x_get_offsets( + u8 output_num, + struct clk_register_offsets *offsets) +{ + switch (output_num) { + case 0: + offsets->oe_offset = IDT24x_REG_OUTEN; + offsets->oe_mask = IDT24x_REG_OUTEN0_MASK; + offsets->dis_mask = IDT24x_REG_Q0_DIS_MASK; + offsets->ns1_offset = IDT24x_REG_NS1_Q0; + offsets->ns1_offset_mask = IDT24x_REG_NS1_Q0_MASK; + offsets->ns2_15_8_offset = IDT24x_REG_NS2_Q0_15_8; + offsets->ns2_7_0_offset = IDT24x_REG_NS2_Q0_7_0; + break; + case 1: + offsets->oe_offset = IDT24x_REG_OUTEN; + offsets->oe_mask = IDT24x_REG_OUTEN1_MASK; + offsets->dis_mask = IDT24x_REG_Q1_DIS_MASK; + offsets->n_17_16_offset = IDT24x_REG_N_Q1_17_16; + offsets->n_17_16_mask = IDT24x_REG_N_Q1_17_16_MASK; + offsets->n_15_8_offset = IDT24x_REG_N_Q1_15_8; + offsets->n_7_0_offset = IDT24x_REG_N_Q1_7_0; + offsets->nfrac_27_24_offset = IDT24x_REG_NFRAC_Q1_27_24; + offsets->nfrac_27_24_mask = + IDT24x_REG_NFRAC_Q1_27_24_MASK; + offsets->nfrac_23_16_offset = IDT24x_REG_NFRAC_Q1_23_16; + offsets->nfrac_15_8_offset = IDT24x_REG_NFRAC_Q1_15_8; + offsets->nfrac_7_0_offset = IDT24x_REG_NFRAC_Q1_7_0; + break; + case 2: + offsets->oe_offset = IDT24x_REG_OUTEN; + offsets->oe_mask = IDT24x_REG_OUTEN2_MASK; + offsets->dis_mask = IDT24x_REG_Q2_DIS_MASK; + offsets->n_17_16_offset = IDT24x_REG_N_Q2_17_16; + offsets->n_17_16_mask = IDT24x_REG_N_Q2_17_16_MASK; + offsets->n_15_8_offset = IDT24x_REG_N_Q2_15_8; + offsets->n_7_0_offset = IDT24x_REG_N_Q2_7_0; + offsets->nfrac_27_24_offset = IDT24x_REG_NFRAC_Q2_27_24; + offsets->nfrac_27_24_mask = + IDT24x_REG_NFRAC_Q2_27_24_MASK; + offsets->nfrac_23_16_offset = IDT24x_REG_NFRAC_Q2_23_16; + offsets->nfrac_15_8_offset = IDT24x_REG_NFRAC_Q2_15_8; + offsets->nfrac_7_0_offset = IDT24x_REG_NFRAC_Q2_7_0; + break; + case 3: + offsets->oe_offset = IDT24x_REG_OUTEN; + offsets->oe_mask = IDT24x_REG_OUTEN3_MASK; + offsets->dis_mask = IDT24x_REG_Q3_DIS_MASK; + offsets->n_17_16_offset = IDT24x_REG_N_Q3_17_16; + offsets->n_17_16_mask = IDT24x_REG_N_Q3_17_16_MASK; + offsets->n_15_8_offset = IDT24x_REG_N_Q3_15_8; + offsets->n_7_0_offset = IDT24x_REG_N_Q3_7_0; + offsets->nfrac_27_24_offset = IDT24x_REG_NFRAC_Q3_27_24; + offsets->nfrac_27_24_mask = + IDT24x_REG_NFRAC_Q3_27_24_MASK; + offsets->nfrac_23_16_offset = IDT24x_REG_NFRAC_Q3_23_16; + offsets->nfrac_15_8_offset = IDT24x_REG_NFRAC_Q3_15_8; + offsets->nfrac_7_0_offset = IDT24x_REG_NFRAC_Q3_7_0; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * idt24x_calc_div_q0 - Calculate dividers and VCO freq to generate + * the specified Q0 frequency. + * @chip: Device data structure. contains all requested frequencies + * for all outputs. + * + * The actual output divider is ns1 * ns2 * 2. fOutput = fVCO / (ns1 * ns2 * 2) + * + * The options for ns1 (when the source is the VCO) are 4,5,6. ns2 is a + * 16-bit value. + * + * chip->divs: structure for specifying ns1/ns2 values. If 0 after this + * function, Q0 is not requested + * + * Return: 0 on success, negative errno otherwise. + */ +static int idt24x_calc_div_q0(struct clk_idt24x_chip *chip) +{ + u8 x; + u32 min_div, max_div, best_vco = 0; + u16 min_ns2, max_ns2; + bool is_lower_vco = false; + + chip->divs.ns1_q0 = 0; + chip->divs.ns2_q0 = 0; + + if (chip->clk[0].requested == 0) + return 0; + + min_div = div64_u64( + (u64)IDT24x_VCO_MIN, chip->clk[0].requested * 2) * 2; + max_div = div64_u64( + (u64)IDT24x_VCO_MAX, chip->clk[0].requested * 2) * 2; + + dev_dbg(&chip->i2c_client->dev, + "%s. requested: %u, min_div: %u, max_div: %u", + __func__, chip->clk[0].requested, min_div, max_div); + + min_ns2 = div64_u64((u64)min_div, IDT24x_MAX_NS1 * 2); + max_ns2 = div64_u64((u64)max_div, IDT24x_MIN_NS1 * 2); + + dev_dbg(&chip->i2c_client->dev, + "%s. min_ns2: %u, max_ns2: %u", __func__, min_ns2, max_ns2); + + for (x = 0; x < ARRAY_SIZE(q0_ns1_options); x++) { + u16 y = min_ns2; + + while (y <= max_ns2) { + u32 actual_div = q0_ns1_options[x] * y * 2; + u32 current_vco = actual_div * + chip->clk[0].requested; + + if (current_vco < IDT24x_VCO_MIN) + dev_dbg(&chip->i2c_client->dev, + "%s. ignore div: (ns1=%u * ns2=%u * 2 * %u) == %u < %u", + __func__, q0_ns1_options[x], y, + chip->clk[0].requested, + current_vco, IDT24x_VCO_MIN); + else if (current_vco > IDT24x_VCO_MAX) { + dev_dbg(&chip->i2c_client->dev, + "%s. ignore div: (ns1=%u * ns2=%u * 2 * %u) == %u > %u. EXIT LOOP.", + __func__, q0_ns1_options[x], y, + chip->clk[0].requested, + current_vco, IDT24x_VCO_MAX); + y = max_ns2; + } else { + bool use = false; + + dev_dbg(&chip->i2c_client->dev, + "%s. contender: (ns1=%u * ns2=%u * 2 * %u) == %u [in range]", + __func__, q0_ns1_options[x], y, + chip->clk[0].requested, + current_vco); + if (current_vco <= IDT24x_VCO_OPT) { + if (current_vco > best_vco || + !is_lower_vco) { + is_lower_vco = true; + use = true; + } + } else if (!is_lower_vco && + current_vco > best_vco) + use = true; + if (use) { + chip->divs.ns1_q0 = x; + chip->divs.ns2_q0 = y; + best_vco = current_vco; + } + } + y++; + } + } + + dev_dbg(&chip->i2c_client->dev, + "%s. best: (ns1=%u [/%u] * ns2=%u * 2 * %u) == %u", + __func__, chip->divs.ns1_q0, q0_ns1_options[chip->divs.ns1_q0], + chip->divs.ns2_q0, chip->clk[0].requested, best_vco); + return 0; +} + +/** + * idt24x_calc_divs - Calculate dividers to generate the specified frequency. + * @chip: Device data structure. contains all requested frequencies + * for all outputs. + * + * Calculate the clock dividers (dsmint, dsmfrac for vco; ns1/ns2 for q0, + * n/nfrac for q1-3) for a given target frequency. + * + * Return: 0 on success, negative errno otherwise. + */ +static int idt24x_calc_divs(struct clk_idt24x_chip *chip) +{ + u32 vco = 0; + int result; + + result = idt24x_calc_div_q0(chip); + if (result < 0) + return result; + + dev_dbg(&chip->i2c_client->dev, + "%s: after idt24x_calc_div_q0. ns1: %u [/%u], ns2: %u", + __func__, chip->divs.ns1_q0, q0_ns1_options[chip->divs.ns1_q0], + chip->divs.ns2_q0); + + chip->divs.dsmint = 0; + chip->divs.dsmfrac = 0; + + if (chip->clk[0].requested > 0) { + /* Q0 is in use and is governing the actual VCO freq */ + vco = q0_ns1_options[chip->divs.ns1_q0] * chip->divs.ns2_q0 * + 2 * chip->clk[0].requested; + } else { + u32 freq = 0; + u32 walk; + u32 min_div, max_div; + bool is_lower_vco = false; + + /* + * Q0 is not in use. Use the first requested (fractional) + * output frequency as the one controlling the VCO. + */ + for (walk = 1; walk < NUM_OUTPUTS; walk++) { + if (chip->clk[walk].requested != 0) { + freq = chip->clk[walk].requested; + break; + } + } + + if (freq == 0) { + dev_err(&chip->i2c_client->dev, + "%s: NO FREQUENCIES SPECIFIED", __func__); + return -EINVAL; + } + + /* + * First, determine the min/max div for the output frequency. + */ + min_div = IDT24x_MIN_INT_DIVIDER; + max_div = div64_u64((u64)IDT24x_VCO_MAX, freq * 2) * 2; + + dev_dbg(&chip->i2c_client->dev, + "%s: calc_divs for fractional output. freq: %u, min_div: %u, max_div: %u", + __func__, freq, min_div, max_div); + + walk = min_div; + + while (walk <= max_div) { + u32 current_vco = freq * walk; + + dev_dbg(&chip->i2c_client->dev, + "%s: calc_divs for fractional output. walk: %u, freq: %u, vco: %u", + __func__, walk, freq, vco); + if (current_vco >= IDT24x_VCO_MIN && + vco <= IDT24x_VCO_MAX) { + if (current_vco <= IDT24x_VCO_OPT) { + if (current_vco > vco || + !is_lower_vco) { + is_lower_vco = true; + vco = current_vco; + } + } else if (!is_lower_vco && current_vco > vco) { + vco = current_vco; + } + } + /* Divider must be even. */ + walk += 2; + } + } + + if (vco != 0) { + u32 pfd; + u64 rem; + int x; + + /* Setup dividers for outputs with fractional dividers. */ + for (x = 1; x < NUM_OUTPUTS; x++) { + if (chip->clk[x].requested != 0) { + /* + * The value written to the chip is half + * the calculated divider. + */ + chip->divs.nint[x - 1] = div64_u64_rem( + (u64)vco, + chip->clk[x].requested * 2, + &rem); + chip->divs.nfrac[x - 1] = div64_u64( + rem * 1 << 28, + chip->clk[x].requested * 2); + dev_dbg(&chip->i2c_client->dev, + "%s: div to get Q%i freq %u from vco %u: int part: %u, rem: %llu, frac part: %u", + __func__, x, + chip->clk[x].requested, + vco, chip->divs.nint[x - 1], rem, + chip->divs.nfrac[x - 1]); + } + } + + /* Calculate freq for pfd */ + pfd = chip->input_clk_freq * (chip->doubler_disabled ? 1 : 2); + + /* + * Calculate dsmint & dsmfrac: + * ----------------------------- + * dsm = float(vco)/float(pfd) + * dsmfrac = dsm-floor(dsm) * 2^21 + * rem = vco % pfd + * therefore: + * dsmfrac = (rem * 2^21)/pfd + */ + chip->divs.dsmint = div64_u64_rem(vco, pfd, &rem); + chip->divs.dsmfrac = div64_u64(rem * 1 << 21, pfd); + + dev_dbg(&chip->i2c_client->dev, + "%s: vco: %u, pfd: %u, dsmint: %u, dsmfrac: %u, rem: %llu", + __func__, vco, pfd, chip->divs.dsmint, + chip->divs.dsmfrac, rem); + } else { + dev_err(&chip->i2c_client->dev, + "%s: no integer divider in range found. NOT SUPPORTED.", + __func__); + return -EINVAL; + } + return 0; +} + +/** + * idt24x_enable_output - Enable/disable a particular output + * @chip: Device data structure + * @output: Output to enable/disable + * @enable: Enable (true/false) + * + * Return: passes on regmap_write return value. + */ +static int idt24x_enable_output( + struct clk_idt24x_chip *chip, u8 output, bool enable) +{ + struct clk_register_offsets offsets; + int err; + struct i2c_client *client = chip->i2c_client; + + /* + * When an output is enabled, enable it in the original + * data read from the chip and cached. Otherwise it may be + * accidentally turned off when another output is enabled. + * + * E.g., the driver starts with all outputs off in reg_out_en_x. + * Q1 is enabled with the appropriate mask. Q2 is then enabled, + * which results in Q1 being turned back off (because Q1 was off + * in reg_out_en_x). + */ + + err = idt24x_get_offsets(output, &offsets); + if (err) { + dev_err(&client->dev, + "%s: error calling idt24x_get_offsets for %d: %i", + __func__, output, err); + return err; + } + + dev_dbg(&client->dev, + "%s: q%u enable? %d. reg_out_en_x before: 0x%x, reg_out_mode_0_1 before: 0x%x, reg_out_mode_2_3 before: 0x%x, reg_qx_dis before: 0x%x", + __func__, output, enable, chip->reg_out_en_x, + chip->reg_out_mode_0_1, chip->reg_out_mode_2_3, + chip->reg_qx_dis); + + chip->reg_out_en_x = chip->reg_out_en_x & ~offsets.oe_mask; + if (enable) + chip->reg_out_en_x |= (1 << bits_to_shift(offsets.oe_mask)); + + chip->reg_qx_dis = chip->reg_qx_dis & ~offsets.dis_mask; + dev_dbg(&client->dev, + "%s: q%u enable? %d. reg_qx_dis mask: 0x%x, before checking enable: 0x%x", + __func__, output, enable, offsets.dis_mask, + chip->reg_qx_dis); + if (!enable) + chip->reg_qx_dis |= (1 << bits_to_shift(offsets.dis_mask)); + + dev_dbg(&client->dev, + "%s: q%u enable? %d. reg_out_en_x after: 0x%x, reg_qx_dis after: 0x%x", + __func__, output, enable, chip->reg_out_en_x, + chip->reg_qx_dis); + + err = i2cwrite( + client, chip->regmap, IDT24x_REG_OUTEN, chip->reg_out_en_x); + if (err) { + dev_err(&client->dev, + "%s: error setting IDT24x_REG_OUTEN: %i", + __func__, err); + return err; + } + + err = i2cwrite( + client, chip->regmap, IDT24x_REG_OUTMODE0_1, + chip->reg_out_mode_0_1); + if (err) { + dev_err(&client->dev, + "%s: error setting IDT24x_REG_OUTMODE0_1: %i", + __func__, err); + return err; + } + + err = i2cwrite( + client, chip->regmap, IDT24x_REG_OUTMODE2_3, + chip->reg_out_mode_2_3); + if (err) { + dev_err(&client->dev, + "%s: error setting IDT24x_REG_OUTMODE2_3: %i", + __func__, err); + return err; + } + + err = i2cwrite( + client, chip->regmap, IDT24x_REG_Q_DIS, chip->reg_qx_dis); + if (err) { + dev_err(&client->dev, + "%s: error setting IDT24x_REG_Q_DIS: %i", + __func__, err); + return err; + } + + return 0; +} + +/** + * idt24x_update_device - write registers to the chip + * @chip: Device data structure + * + * Write all values to hardware that we have calculated. + * + * Return: passes on regmap_bulk_write return value. + */ +static int idt24x_update_device(struct clk_idt24x_chip *chip) +{ + int err; + struct i2c_client *client = chip->i2c_client; + int x = -1; + + dev_dbg(&client->dev, + "%s: setting DSM_INT_8 (val %u @ %u)", + __func__, chip->divs.dsmint >> 8, + IDT24x_REG_DSM_INT_8); + err = i2cwritewithmask( + client, chip->regmap, IDT24x_REG_DSM_INT_8, + (chip->divs.dsmint >> 8) & IDT24x_REG_DSM_INT_8_MASK, + chip->reg_dsm_int_8, IDT24x_REG_DSM_INT_8_MASK); + if (err) { + dev_err(&client->dev, + "%s: error setting IDT24x_REG_DSM_INT_8: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting DSM_INT_7_0 (val %u @ 0x%x)", + __func__, chip->divs.dsmint & 0xFF, + IDT24x_REG_DSM_INT_7_0); + err = i2cwrite( + client, chip->regmap, IDT24x_REG_DSM_INT_7_0, + chip->divs.dsmint & 0xFF); + if (err) { + dev_err(&client->dev, + "%s: error setting IDT24x_REG_DSM_INT_7_0: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting IDT24x_REG_DSMFRAC_20_16 (val %u @ 0x%x)", + __func__, chip->divs.dsmfrac >> 16, + IDT24x_REG_DSMFRAC_20_16); + err = i2cwritewithmask( + client, chip->regmap, IDT24x_REG_DSMFRAC_20_16, + (chip->divs.dsmfrac >> 16) & IDT24x_REG_DSMFRAC_20_16_MASK, + chip->reg_dsm_int_8, IDT24x_REG_DSMFRAC_20_16_MASK); + if (err) { + dev_err(&client->dev, + "%s: error setting IDT24x_REG_DSMFRAC_20_16: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting IDT24x_REG_DSMFRAC_15_8 (val %u @ 0x%x)", + __func__, (chip->divs.dsmfrac >> 8) & 0xFF, + IDT24x_REG_DSMFRAC_15_8); + err = i2cwrite( + client, chip->regmap, IDT24x_REG_DSMFRAC_15_8, + (chip->divs.dsmfrac >> 8) & 0xFF); + if (err) { + dev_err(&client->dev, + "%s: error setting IDT24x_REG_DSMFRAC_15_8: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting IDT24x_REG_DSMFRAC_7_0 (val %u @ 0x%x)", + __func__, chip->divs.dsmfrac & 0xFF, + IDT24x_REG_DSMFRAC_7_0); + err = i2cwrite( + client, chip->regmap, IDT24x_REG_DSMFRAC_7_0, + chip->divs.dsmfrac & 0xFF); + if (err) { + dev_err(&client->dev, + "%s: error setting IDT24x_REG_DSMFRAC_7_0: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting IDT24x_REG_NS1_Q0 (val %u @ 0x%x)", + __func__, chip->divs.ns1_q0, IDT24x_REG_NS1_Q0); + err = i2cwritewithmask( + client, chip->regmap, IDT24x_REG_NS1_Q0, + chip->divs.ns1_q0 & IDT24x_REG_NS1_Q0_MASK, + chip->reg_ns1_q0, IDT24x_REG_NS1_Q0_MASK); + if (err) { + dev_err(&client->dev, + "%s: error setting IDT24x_REG_NS1_Q0: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting IDT24x_REG_NS2_Q0_15_8 (val %u @ 0x%x)", + __func__, (chip->divs.ns2_q0 >> 8) & 0xFF, + IDT24x_REG_NS2_Q0_15_8); + err = i2cwrite( + client, chip->regmap, IDT24x_REG_NS2_Q0_15_8, + (chip->divs.ns2_q0 >> 8) & 0xFF); + if (err) { + dev_err(&client->dev, + "%s: error setting IDT24x_REG_NS2_Q0_15_8: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting IDT24x_REG_NS2_Q0_7_0 (val %u @ 0x%x)", + __func__, chip->divs.ns2_q0 & 0xFF, + IDT24x_REG_NS2_Q0_7_0); + err = i2cwrite( + client, chip->regmap, IDT24x_REG_NS2_Q0_7_0, + chip->divs.ns2_q0 & 0xFF); + if (err) { + dev_err(&client->dev, + "%s: error setting IDT24x_REG_NS2_Q0_7_0: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: calling idt24x_enable_output for Q0. requestedFreq: %u", + __func__, chip->clk[0].requested); + idt24x_enable_output(chip, 0, chip->clk[0].requested != 0); + + dev_dbg(&client->dev, + "%s: writing values for q1-q3", __func__); + for (x = 1; x < NUM_OUTPUTS; x++) { + struct clk_register_offsets offsets; + + if (chip->clk[x].requested != 0) { + dev_dbg(&client->dev, + "%s: calling idt24x_get_offsets for %u", + __func__, x); + err = idt24x_get_offsets(x, &offsets); + if (err) { + dev_err(&client->dev, + "%s: error calling idt24x_get_offsets: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: (q%u, nint: %u, nfrac: %u)", + __func__, x, chip->divs.nint[x - 1], + chip->divs.nfrac[x - 1]); + + dev_dbg(&client->dev, + "%s: setting n_17_16_offset (q%u, val %u @ 0x%x)", + __func__, x, + chip->divs.nint[x - 1] >> 16, + offsets.n_17_16_offset); + err = i2cwritewithmask( + client, chip->regmap, offsets.n_17_16_offset, + (chip->divs.nint[x - 1] >> 16) & + offsets.n_17_16_mask, + chip->reg_n_qx_17_16[x - 1], + offsets.n_17_16_mask); + if (err) { + dev_err(&client->dev, + "%s: error setting n_17_16_offset: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting n_15_8_offset (q%u, val %u @ 0x%x)", + __func__, x, + (chip->divs.nint[x - 1] >> 8) & 0xFF, + offsets.n_15_8_offset); + err = i2cwrite( + client, chip->regmap, offsets.n_15_8_offset, + (chip->divs.nint[x - 1] >> 8) & 0xFF); + if (err) { + dev_err(&client->dev, + "%s: error setting n_15_8_offset: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting n_7_0_offset (q%u, val %u @ 0x%x)", + __func__, x, + chip->divs.nint[x - 1] & 0xFF, + offsets.n_7_0_offset); + err = i2cwrite( + client, chip->regmap, offsets.n_7_0_offset, + chip->divs.nint[x - 1] & 0xFF); + if (err) { + dev_err(&client->dev, + "%s: error setting n_7_0_offset: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting nfrac_27_24_offset (q%u, val %u @ 0x%x)", + __func__, x, + (chip->divs.nfrac[x - 1] >> 24), + offsets.nfrac_27_24_offset); + err = i2cwritewithmask( + client, chip->regmap, + offsets.nfrac_27_24_offset, + (chip->divs.nfrac[x - 1] >> 24) & + offsets.nfrac_27_24_mask, + chip->reg_nfrac_qx_27_24[x - 1], + offsets.nfrac_27_24_mask); + if (err) { + dev_err(&client->dev, + "%s: error setting nfrac_27_24_offset: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting nfrac_23_16_offset (q%u, val %u @ 0x%x)", + __func__, x, + (chip->divs.nfrac[x - 1] >> 16) & 0xFF, + offsets.nfrac_23_16_offset); + err = i2cwrite( + client, chip->regmap, + offsets.nfrac_23_16_offset, + (chip->divs.nfrac[x - 1] >> 16) & 0xFF); + if (err) { + dev_err(&client->dev, + "%s: error setting nfrac_23_16_offset: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting nfrac_15_8_offset (q%u, val %u @ 0x%x)", + __func__, x, + (chip->divs.nfrac[x - 1] >> 8) & 0xFF, + offsets.nfrac_15_8_offset); + err = i2cwrite( + client, chip->regmap, + offsets.nfrac_15_8_offset, + (chip->divs.nfrac[x - 1] >> 8) & 0xFF); + if (err) { + dev_err(&client->dev, + "%s: error setting nfrac_15_8_offset: %i", + __func__, err); + return err; + } + + dev_dbg(&client->dev, + "%s: setting nfrac_7_0_offset (q%u, val %u @ 0x%x)", + __func__, x, + chip->divs.nfrac[x - 1] & 0xFF, + offsets.nfrac_7_0_offset); + err = i2cwrite( + client, chip->regmap, offsets.nfrac_7_0_offset, + chip->divs.nfrac[x - 1] & 0xFF); + if (err) { + dev_err(&client->dev, + "%s: error setting nfrac_7_0_offset: %i", + __func__, err); + return err; + } + } + idt24x_enable_output(chip, x, + chip->clk[x].requested != 0); + chip->clk[x].actual = chip->clk[x].requested; + } + return 0; +} + +/** + * idt24x_set_frequency - Adjust output frequency on the attached chip. + * @chip: Device data structure, including all requested frequencies. + * + * Return: 0 on success. + */ +int idt24x_set_frequency(struct clk_idt24x_chip *chip) +{ + int err; + struct i2c_client *client = chip->i2c_client; + int x; + bool all_disabled = true; + + for (x = 0; x < NUM_OUTPUTS; x++) { + if (chip->clk[x].requested == 0) { + idt24x_enable_output(chip, x, false); + chip->clk[x].actual = 0; + } else { + all_disabled = false; + } + } + + if (all_disabled) + /* + * no requested frequencies, so nothing else to calculate + * or write to the chip. If the consumer wants to disable + * all outputs, they can request 0 for all frequencies. + */ + return 0; + + if (chip->input_clk_freq == 0) { + dev_err(&client->dev, + "%s: no input frequency; can't continue.", __func__); + return -EINVAL; + } + + err = idt24x_calc_divs(chip); + if (err) { + dev_err(&client->dev, + "%s: error calling idt24x_calc_divs: %i", + __func__, err); + return err; + } + + err = idt24x_update_device(chip); + if (err) { + dev_err(&client->dev, + "%s: error updating the device: %i", + __func__, err); + return err; + } + + return 0; +} |