bdc753c7fc
late breaking reports that a patch series to rework clk rate range support broke boot on some devices, so I've left that branch out of this PR. Hopefully we can get to that next week, or punt on it and let it bake another cycle. That means we don't really have any changes to the core framework this time around besides a few typo fixes. Instead this is all clk driver updates and fixes. The usual suspects are here (again), with Qualcomm dominating the diffstat. We look to have gained support for quite a few new Qualcomm SoCs and Dmitry worked on updating many of the existing Qualcomm drivers to use clk_parent_data. After that we have MediaTek drivers getting some much needed updates, in particular to support GPU DVFS. There are also quite a few Samsung clk driver patches, but that's mostly because there was a maintainer change and so last release we missed some of those patches. Overall things look normal, but I'm slowly reviewing core framework code nowadays and that shows given the rate range patches had to be yanked last minute. Let's hope this situation changes soon. New Drivers: - Support for Renesas VersaClock7 clock generator family - Add Spreadtrum UMS512 SoC clk support - New clock drivers for MediaTek Helio X10 MT6795 - Display clks for Qualcomm SM6115, SM8450 - GPU clks for Qualcomm SC8280XP - Qualcomm MSM8909 and SM6375 global and SMD RPM clk drivers Deleted Drivers: - Remove DaVinci DM644x and DM646x clk driver support Updates: - Convert Baikal-T1 CCU driver to platform driver - Split reset support out of primary Baikal-T1 CCU driver - Add some missing clks required for RPiVid Video Decoder on RaspberryPi - Mark PLLC critical on bcm2835 - More devm helpers for fixed rate registration - Various PXA168 clk driver fixes - Add resets for MediaTek MT8195 PCIe and USB - Miscellaneous of_node_put() fixes - Nuke dt-bindings/clk path (again) by moving headers to dt-bindings/clock - Convert gpio-clk-gate binding to YAML - Various fixes to AMD/Xilinx Zynqmp clk driver - Graduate AMD/Xilinx "clocking wizard" driver from staging - Add missing DPI1_HDMI clock in MT8195 VDOSYS1 - Clock driver changes to support GPU DVFS on MT8183, MT8192, MT8195 - Fix GPU clock topology on MT8195 - Propogate rate changes from GPU clock gate up the tree - Clock mux notifiers for GPU-related PLLs - Conversion of more "simple" drivers to mtk_clk_simple_probe() - Hook up mtk_clk_simple_remove() for "simple" MT8192 clock drivers - Fixes to previous |struct clk| to |struct clk_hw| conversion on MediaTek - Shrink MT8192 clock driver by deduplicating clock parent lists - Change order between 'sim_enet_root_clk' and 'enet_qos_root_clk' clocks for i.MX8MP - Drop unnecessary newline in i.MX8MM dt-bindings - Add more MU1 and SAI clocks dt-bindings Ids - Introduce slice busy bit check for i.MX93 composite clock - Introduce white list bit check for i.MX93 composite clock - Add new i.MX93 clock gate - Add MU1 and MU2 clocks to i.MX93 clock provider - Add SAI IPG clocks to i.MX93 clock provider - add generic clocks for U(S)ART available on SAMA5D2 SoCs - reset controller support for Polarfire clocks - .round_rate and .set rate support for clk-mpfs - code cleanup for clk-mpfs - PLL support for PolarFire SoC's Clock Conditioning Circuitry - Add watchdog, I2C, pin control/GPIO, and Ethernet clocks on R-Car V4H - Add SDHI, Timer (CMT/TMU), and SPI (MSIOF) clocks on R-Car S4-8 - Add I2C clocks and resets on RZ/V2M - Document clock support for the RZ/Five SoC - mux-variant clock using the table variant to select parents - clock controller for the rv1126 soc - conversion of rk3128 to yaml and relicensing of the yaml bindings to gpl2+MIT (following dt-binding guildelines) - Exynos7885: add FSYS, TREX and MFC clock controllers - Exynos850: add IS and AUD (audio) clock controllers with bindings - ExynosAutov9: add FSYS clock controllers with bindings - ExynosAutov9: correct clock IDs in bindings of Peric 0 and 1 clock controllers, due to duplicated entries. This is an acceptable ABI break: recently developed/added platform so without legacies, acked by known users/developers - ExynosAutov9: add few missing Peric 0/1 gates - ExynosAutov9: correct register offsets of few Peric 0/1 clocks - Minor code improvements (use of_device_get_match_data() helper, code style) - Add Krzysztof Kozlowski as co-maintainer of Samsung SoC clocks, as he already maintainers that architecture/platform - Keep Qualcomm GDSCs enabled when PWRSTS_RET flag is there, solving retention issues during suspend of USB on Qualcomm sc7180/sc7280 and SC8280XP - Qualcomm SM6115 and QCM2260 are moved to reuse PLL configuration - Qualcomm SDM660 SDCC1 moved to floor clk ops - Support for the APCS PLLs for Qualcomm IPQ8064, IPQ8074 and IPQ6018 was added/fixed - The Qualcomm MSM8996 CPU clocks are updated with support for ACD - Support for Qualcomm SDM670 GCC and RPMh clks was added - Transition to parent_data, parent_hws and use of ARRAY_SIZE() for num_parents was done for many Qualcomm SoCs - Support for per-reset defined delay on Qualcomm was introduced -----BEGIN PGP SIGNATURE----- iQJFBAABCAAvFiEE9L57QeeUxqYDyoaDrQKIl8bklSUFAmM/trwRHHNib3lkQGtl cm5lbC5vcmcACgkQrQKIl8bklSUEoA/+LiftbrF8Xtu7lGdxRjqLzRftUmHaUQWO d0cadtzMsgxzFJsxp99IiJBVJoaYCBOGlnZDx8p/JGv+mmdhl5+yHgKQbR8nEmTk 5A+bdA1okOdm8SPBPMcLvuMjsgmx+DHkuxvnC2hT8ZGfQDoa+6PnObpP30LJkHT0 oVY8g8ScEuHI5eJcNz3UgxAetKeJd+WRQPxKCrjsOeyhWuNAJ7wdTVQjjzH49X4C RS3fjeHvhr2VZm23IgildY++a6hPO72gtBjEpDRoFwnmWAVqUtxiwptoJJNkC5kB toD/ndQHOLh/XOJFKgksS20L4JHtSp5F3Ma8sIuOjAXmDCyqMdTQhydnl5Pyrow+ ct8BMUGkx0Sw8pXBJYINtHpwTtIxvLu/sBNqBb/lRCWd8byrPlUnKvF/COcoxp27 miZTwJI28fHU5a2K/46iWZCI5YUvVcnBSz8WbEWWvOltIT8S0JvZozA3KuRm5vys /k2HaQwO2I0QWQzPjfg6SRlTTWH6p+Hc47fSg7LSM6Scsb7ZraajTM2QOvgn7Mgp m/136q7jr9mvuLqqy1fBY3F2hDZYNSJX+UfmIFcpCyxvht0GVFN9YCc+Ibgyl2vQ P3b9LXV2OqhtDJg6ds7v8aPgAGUwUFO8GTPBG1cuom7z5u/kdIpjKaFAyr8wWSuJ wqPIFevggsA= =9jI+ -----END PGP SIGNATURE----- Merge tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux Pull clk updates from Stephen Boyd: "We have some late breaking reports that a patch series to rework clk rate range support broke boot on some devices, so I've left that branch out of this. Hopefully we can get to that next week, or punt on it and let it bake another cycle. That means we don't really have any changes to the core framework this time around besides a few typo fixes. Instead this is all clk driver updates and fixes. The usual suspects are here (again), with Qualcomm dominating the diffstat. We look to have gained support for quite a few new Qualcomm SoCs and Dmitry worked on updating many of the existing Qualcomm drivers to use clk_parent_data. After that we have MediaTek drivers getting some much needed updates, in particular to support GPU DVFS. There are also quite a few Samsung clk driver patches, but that's mostly because there was a maintainer change and so last release we missed some of those patches. Overall things look normal, but I'm slowly reviewing core framework code nowadays and that shows given the rate range patches had to be yanked last minute. Let's hope this situation changes soon. New Drivers: - Support for Renesas VersaClock7 clock generator family - Add Spreadtrum UMS512 SoC clk support - New clock drivers for MediaTek Helio X10 MT6795 - Display clks for Qualcomm SM6115, SM8450 - GPU clks for Qualcomm SC8280XP - Qualcomm MSM8909 and SM6375 global and SMD RPM clk drivers Deleted Drivers: - Remove DaVinci DM644x and DM646x clk driver support Updates: - Convert Baikal-T1 CCU driver to platform driver - Split reset support out of primary Baikal-T1 CCU driver - Add some missing clks required for RPiVid Video Decoder on RaspberryPi - Mark PLLC critical on bcm2835 - More devm helpers for fixed rate registration - Various PXA168 clk driver fixes - Add resets for MediaTek MT8195 PCIe and USB - Miscellaneous of_node_put() fixes - Nuke dt-bindings/clk path (again) by moving headers to dt-bindings/clock - Convert gpio-clk-gate binding to YAML - Various fixes to AMD/Xilinx Zynqmp clk driver - Graduate AMD/Xilinx "clocking wizard" driver from staging - Add missing DPI1_HDMI clock in MT8195 VDOSYS1 - Clock driver changes to support GPU DVFS on MT8183, MT8192, MT8195 - Fix GPU clock topology on MT8195 - Propogate rate changes from GPU clock gate up the tree - Clock mux notifiers for GPU-related PLLs - Conversion of more "simple" drivers to mtk_clk_simple_probe() - Hook up mtk_clk_simple_remove() for "simple" MT8192 clock drivers - Fixes to previous |struct clk| to |struct clk_hw| conversion on MediaTek - Shrink MT8192 clock driver by deduplicating clock parent lists - Change order between 'sim_enet_root_clk' and 'enet_qos_root_clk' clocks for i.MX8MP - Drop unnecessary newline in i.MX8MM dt-bindings - Add more MU1 and SAI clocks dt-bindings Ids - Introduce slice busy bit check for i.MX93 composite clock - Introduce white list bit check for i.MX93 composite clock - Add new i.MX93 clock gate - Add MU1 and MU2 clocks to i.MX93 clock provider - Add SAI IPG clocks to i.MX93 clock provider - add generic clocks for U(S)ART available on SAMA5D2 SoCs - reset controller support for Polarfire clocks - .round_rate and .set rate support for clk-mpfs - code cleanup for clk-mpfs - PLL support for PolarFire SoC's Clock Conditioning Circuitry - Add watchdog, I2C, pin control/GPIO, and Ethernet clocks on R-Car V4H - Add SDHI, Timer (CMT/TMU), and SPI (MSIOF) clocks on R-Car S4-8 - Add I2C clocks and resets on RZ/V2M - Document clock support for the RZ/Five SoC - mux-variant clock using the table variant to select parents - clock controller for the rv1126 soc - conversion of rk3128 to yaml and relicensing of the yaml bindings to gpl2+MIT (following dt-binding guildelines) - Exynos7885: add FSYS, TREX and MFC clock controllers - Exynos850: add IS and AUD (audio) clock controllers with bindings - ExynosAutov9: add FSYS clock controllers with bindings - ExynosAutov9: correct clock IDs in bindings of Peric 0 and 1 clock controllers, due to duplicated entries. This is an acceptable ABI break: recently developed/added platform so without legacies, acked by known users/developers - ExynosAutov9: add few missing Peric 0/1 gates - ExynosAutov9: correct register offsets of few Peric 0/1 clocks - Minor code improvements (use of_device_get_match_data() helper, code style) - Add Krzysztof Kozlowski as co-maintainer of Samsung SoC clocks, as he already maintainers that architecture/platform - Keep Qualcomm GDSCs enabled when PWRSTS_RET flag is there, solving retention issues during suspend of USB on Qualcomm sc7180/sc7280 and SC8280XP - Qualcomm SM6115 and QCM2260 are moved to reuse PLL configuration - Qualcomm SDM660 SDCC1 moved to floor clk ops - Support for the APCS PLLs for Qualcomm IPQ8064, IPQ8074 and IPQ6018 was added/fixed - The Qualcomm MSM8996 CPU clocks are updated with support for ACD - Support for Qualcomm SDM670 GCC and RPMh clks was added - Transition to parent_data, parent_hws and use of ARRAY_SIZE() for num_parents was done for many Qualcomm SoCs - Support for per-reset defined delay on Qualcomm was introduced" * tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux: (283 commits) clk: qcom: gcc-sm6375: Ensure unsigned long type clk: qcom: gcc-sm6375: Remove unused variables clk: qcom: kpss-xcc: convert to parent data API clk: introduce (devm_)hw_register_mux_parent_data_table API clk: allow building lan966x as a module clk: clk-xgene: simplify if-if to if-else clk: ast2600: BCLK comes from EPLL clk: clocking-wizard: Depend on HAS_IOMEM clk: clocking-wizard: Use dev_err_probe() helper clk: nxp: fix typo in comment clk: pxa: add a check for the return value of kzalloc() clk: vc5: Add support for IDT/Renesas VersaClock 5P49V6975 dt-bindings: clock: vc5: Add 5P49V6975 clk: mvebu: armada-37xx-tbg: Remove the unneeded result variable clk: ti: dra7-atl: Fix reference leak in of_dra7_atl_clk_probe clk: Renesas versaclock7 ccf device driver dt-bindings: Renesas versaclock7 device tree bindings clk: ti: Balance of_node_get() calls for of_find_node_by_name() clk: imx: scu: fix memleak on platform_device_add() fails clk: vc5: Use regmap_{set,clear}_bits() where appropriate ...
1301 lines
36 KiB
C
1301 lines
36 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Driver for IDT Versaclock 5
|
|
*
|
|
* Copyright (C) 2017 Marek Vasut <marek.vasut@gmail.com>
|
|
*/
|
|
|
|
/*
|
|
* Possible optimizations:
|
|
* - Use spread spectrum
|
|
* - Use integer divider in FOD if applicable
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/rational.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <dt-bindings/clock/versaclock.h>
|
|
|
|
/* VersaClock5 registers */
|
|
#define VC5_OTP_CONTROL 0x00
|
|
|
|
/* Factory-reserved register block */
|
|
#define VC5_RSVD_DEVICE_ID 0x01
|
|
#define VC5_RSVD_ADC_GAIN_7_0 0x02
|
|
#define VC5_RSVD_ADC_GAIN_15_8 0x03
|
|
#define VC5_RSVD_ADC_OFFSET_7_0 0x04
|
|
#define VC5_RSVD_ADC_OFFSET_15_8 0x05
|
|
#define VC5_RSVD_TEMPY 0x06
|
|
#define VC5_RSVD_OFFSET_TBIN 0x07
|
|
#define VC5_RSVD_GAIN 0x08
|
|
#define VC5_RSVD_TEST_NP 0x09
|
|
#define VC5_RSVD_UNUSED 0x0a
|
|
#define VC5_RSVD_BANDGAP_TRIM_UP 0x0b
|
|
#define VC5_RSVD_BANDGAP_TRIM_DN 0x0c
|
|
#define VC5_RSVD_CLK_R_12_CLK_AMP_4 0x0d
|
|
#define VC5_RSVD_CLK_R_34_CLK_AMP_4 0x0e
|
|
#define VC5_RSVD_CLK_AMP_123 0x0f
|
|
|
|
/* Configuration register block */
|
|
#define VC5_PRIM_SRC_SHDN 0x10
|
|
#define VC5_PRIM_SRC_SHDN_EN_XTAL BIT(7)
|
|
#define VC5_PRIM_SRC_SHDN_EN_CLKIN BIT(6)
|
|
#define VC5_PRIM_SRC_SHDN_EN_DOUBLE_XTAL_FREQ BIT(3)
|
|
#define VC5_PRIM_SRC_SHDN_SP BIT(1)
|
|
#define VC5_PRIM_SRC_SHDN_EN_GBL_SHDN BIT(0)
|
|
|
|
#define VC5_VCO_BAND 0x11
|
|
#define VC5_XTAL_X1_LOAD_CAP 0x12
|
|
#define VC5_XTAL_X2_LOAD_CAP 0x13
|
|
#define VC5_REF_DIVIDER 0x15
|
|
#define VC5_REF_DIVIDER_SEL_PREDIV2 BIT(7)
|
|
#define VC5_REF_DIVIDER_REF_DIV(n) ((n) & 0x3f)
|
|
|
|
#define VC5_VCO_CTRL_AND_PREDIV 0x16
|
|
#define VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV BIT(7)
|
|
|
|
#define VC5_FEEDBACK_INT_DIV 0x17
|
|
#define VC5_FEEDBACK_INT_DIV_BITS 0x18
|
|
#define VC5_FEEDBACK_FRAC_DIV(n) (0x19 + (n))
|
|
#define VC5_RC_CONTROL0 0x1e
|
|
#define VC5_RC_CONTROL1 0x1f
|
|
|
|
/* These registers are named "Unused Factory Reserved Registers" */
|
|
#define VC5_RESERVED_X0(idx) (0x20 + ((idx) * 0x10))
|
|
#define VC5_RESERVED_X0_BYPASS_SYNC BIT(7) /* bypass_sync<idx> bit */
|
|
|
|
/* Output divider control for divider 1,2,3,4 */
|
|
#define VC5_OUT_DIV_CONTROL(idx) (0x21 + ((idx) * 0x10))
|
|
#define VC5_OUT_DIV_CONTROL_RESET BIT(7)
|
|
#define VC5_OUT_DIV_CONTROL_SELB_NORM BIT(3)
|
|
#define VC5_OUT_DIV_CONTROL_SEL_EXT BIT(2)
|
|
#define VC5_OUT_DIV_CONTROL_INT_MODE BIT(1)
|
|
#define VC5_OUT_DIV_CONTROL_EN_FOD BIT(0)
|
|
|
|
#define VC5_OUT_DIV_FRAC(idx, n) (0x22 + ((idx) * 0x10) + (n))
|
|
#define VC5_OUT_DIV_FRAC4_OD_SCEE BIT(1)
|
|
|
|
#define VC5_OUT_DIV_STEP_SPREAD(idx, n) (0x26 + ((idx) * 0x10) + (n))
|
|
#define VC5_OUT_DIV_SPREAD_MOD(idx, n) (0x29 + ((idx) * 0x10) + (n))
|
|
#define VC5_OUT_DIV_SKEW_INT(idx, n) (0x2b + ((idx) * 0x10) + (n))
|
|
#define VC5_OUT_DIV_INT(idx, n) (0x2d + ((idx) * 0x10) + (n))
|
|
#define VC5_OUT_DIV_SKEW_FRAC(idx) (0x2f + ((idx) * 0x10))
|
|
|
|
/* Clock control register for clock 1,2 */
|
|
#define VC5_CLK_OUTPUT_CFG(idx, n) (0x60 + ((idx) * 0x2) + (n))
|
|
#define VC5_CLK_OUTPUT_CFG0_CFG_SHIFT 5
|
|
#define VC5_CLK_OUTPUT_CFG0_CFG_MASK GENMASK(7, VC5_CLK_OUTPUT_CFG0_CFG_SHIFT)
|
|
|
|
#define VC5_CLK_OUTPUT_CFG0_CFG_LVPECL (VC5_LVPECL)
|
|
#define VC5_CLK_OUTPUT_CFG0_CFG_CMOS (VC5_CMOS)
|
|
#define VC5_CLK_OUTPUT_CFG0_CFG_HCSL33 (VC5_HCSL33)
|
|
#define VC5_CLK_OUTPUT_CFG0_CFG_LVDS (VC5_LVDS)
|
|
#define VC5_CLK_OUTPUT_CFG0_CFG_CMOS2 (VC5_CMOS2)
|
|
#define VC5_CLK_OUTPUT_CFG0_CFG_CMOSD (VC5_CMOSD)
|
|
#define VC5_CLK_OUTPUT_CFG0_CFG_HCSL25 (VC5_HCSL25)
|
|
|
|
#define VC5_CLK_OUTPUT_CFG0_PWR_SHIFT 3
|
|
#define VC5_CLK_OUTPUT_CFG0_PWR_MASK GENMASK(4, VC5_CLK_OUTPUT_CFG0_PWR_SHIFT)
|
|
#define VC5_CLK_OUTPUT_CFG0_PWR_18 (0<<VC5_CLK_OUTPUT_CFG0_PWR_SHIFT)
|
|
#define VC5_CLK_OUTPUT_CFG0_PWR_25 (2<<VC5_CLK_OUTPUT_CFG0_PWR_SHIFT)
|
|
#define VC5_CLK_OUTPUT_CFG0_PWR_33 (3<<VC5_CLK_OUTPUT_CFG0_PWR_SHIFT)
|
|
#define VC5_CLK_OUTPUT_CFG0_SLEW_SHIFT 0
|
|
#define VC5_CLK_OUTPUT_CFG0_SLEW_MASK GENMASK(1, VC5_CLK_OUTPUT_CFG0_SLEW_SHIFT)
|
|
#define VC5_CLK_OUTPUT_CFG0_SLEW_80 (0<<VC5_CLK_OUTPUT_CFG0_SLEW_SHIFT)
|
|
#define VC5_CLK_OUTPUT_CFG0_SLEW_85 (1<<VC5_CLK_OUTPUT_CFG0_SLEW_SHIFT)
|
|
#define VC5_CLK_OUTPUT_CFG0_SLEW_90 (2<<VC5_CLK_OUTPUT_CFG0_SLEW_SHIFT)
|
|
#define VC5_CLK_OUTPUT_CFG0_SLEW_100 (3<<VC5_CLK_OUTPUT_CFG0_SLEW_SHIFT)
|
|
#define VC5_CLK_OUTPUT_CFG1_EN_CLKBUF BIT(0)
|
|
|
|
#define VC5_CLK_OE_SHDN 0x68
|
|
#define VC5_CLK_OS_SHDN 0x69
|
|
|
|
#define VC5_GLOBAL_REGISTER 0x76
|
|
#define VC5_GLOBAL_REGISTER_GLOBAL_RESET BIT(5)
|
|
|
|
/* PLL/VCO runs between 2.5 GHz and 3.0 GHz */
|
|
#define VC5_PLL_VCO_MIN 2500000000UL
|
|
#define VC5_PLL_VCO_MAX 3000000000UL
|
|
|
|
/* VC5 Input mux settings */
|
|
#define VC5_MUX_IN_XIN BIT(0)
|
|
#define VC5_MUX_IN_CLKIN BIT(1)
|
|
|
|
/* Maximum number of clk_out supported by this driver */
|
|
#define VC5_MAX_CLK_OUT_NUM 5
|
|
|
|
/* Maximum number of FODs supported by this driver */
|
|
#define VC5_MAX_FOD_NUM 4
|
|
|
|
/* flags to describe chip features */
|
|
/* chip has built-in oscilator */
|
|
#define VC5_HAS_INTERNAL_XTAL BIT(0)
|
|
/* chip has PFD requency doubler */
|
|
#define VC5_HAS_PFD_FREQ_DBL BIT(1)
|
|
/* chip has bits to disable FOD sync */
|
|
#define VC5_HAS_BYPASS_SYNC_BIT BIT(2)
|
|
|
|
/* Supported IDT VC5 models. */
|
|
enum vc5_model {
|
|
IDT_VC5_5P49V5923,
|
|
IDT_VC5_5P49V5925,
|
|
IDT_VC5_5P49V5933,
|
|
IDT_VC5_5P49V5935,
|
|
IDT_VC6_5P49V6901,
|
|
IDT_VC6_5P49V6965,
|
|
IDT_VC6_5P49V6975,
|
|
};
|
|
|
|
/* Structure to describe features of a particular VC5 model */
|
|
struct vc5_chip_info {
|
|
const enum vc5_model model;
|
|
const unsigned int clk_fod_cnt;
|
|
const unsigned int clk_out_cnt;
|
|
const u32 flags;
|
|
};
|
|
|
|
struct vc5_driver_data;
|
|
|
|
struct vc5_hw_data {
|
|
struct clk_hw hw;
|
|
struct vc5_driver_data *vc5;
|
|
u32 div_int;
|
|
u32 div_frc;
|
|
unsigned int num;
|
|
};
|
|
|
|
struct vc5_out_data {
|
|
struct clk_hw hw;
|
|
struct vc5_driver_data *vc5;
|
|
unsigned int num;
|
|
unsigned int clk_output_cfg0;
|
|
unsigned int clk_output_cfg0_mask;
|
|
};
|
|
|
|
struct vc5_driver_data {
|
|
struct i2c_client *client;
|
|
struct regmap *regmap;
|
|
const struct vc5_chip_info *chip_info;
|
|
|
|
struct clk *pin_xin;
|
|
struct clk *pin_clkin;
|
|
unsigned char clk_mux_ins;
|
|
struct clk_hw clk_mux;
|
|
struct clk_hw clk_mul;
|
|
struct clk_hw clk_pfd;
|
|
struct vc5_hw_data clk_pll;
|
|
struct vc5_hw_data clk_fod[VC5_MAX_FOD_NUM];
|
|
struct vc5_out_data clk_out[VC5_MAX_CLK_OUT_NUM];
|
|
};
|
|
|
|
/*
|
|
* VersaClock5 i2c regmap
|
|
*/
|
|
static bool vc5_regmap_is_writeable(struct device *dev, unsigned int reg)
|
|
{
|
|
/* Factory reserved regs, make them read-only */
|
|
if (reg <= 0xf)
|
|
return false;
|
|
|
|
/* Factory reserved regs, make them read-only */
|
|
if (reg == 0x14 || reg == 0x1c || reg == 0x1d)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static const struct regmap_config vc5_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
.cache_type = REGCACHE_RBTREE,
|
|
.max_register = 0x76,
|
|
.writeable_reg = vc5_regmap_is_writeable,
|
|
};
|
|
|
|
/*
|
|
* VersaClock5 input multiplexer between XTAL and CLKIN divider
|
|
*/
|
|
static unsigned char vc5_mux_get_parent(struct clk_hw *hw)
|
|
{
|
|
struct vc5_driver_data *vc5 =
|
|
container_of(hw, struct vc5_driver_data, clk_mux);
|
|
const u8 mask = VC5_PRIM_SRC_SHDN_EN_XTAL | VC5_PRIM_SRC_SHDN_EN_CLKIN;
|
|
unsigned int src;
|
|
int ret;
|
|
|
|
ret = regmap_read(vc5->regmap, VC5_PRIM_SRC_SHDN, &src);
|
|
if (ret)
|
|
return 0;
|
|
|
|
src &= mask;
|
|
|
|
if (src == VC5_PRIM_SRC_SHDN_EN_XTAL)
|
|
return 0;
|
|
|
|
if (src == VC5_PRIM_SRC_SHDN_EN_CLKIN)
|
|
return 1;
|
|
|
|
dev_warn(&vc5->client->dev,
|
|
"Invalid clock input configuration (%02x)\n", src);
|
|
return 0;
|
|
}
|
|
|
|
static int vc5_mux_set_parent(struct clk_hw *hw, u8 index)
|
|
{
|
|
struct vc5_driver_data *vc5 =
|
|
container_of(hw, struct vc5_driver_data, clk_mux);
|
|
const u8 mask = VC5_PRIM_SRC_SHDN_EN_XTAL | VC5_PRIM_SRC_SHDN_EN_CLKIN;
|
|
u8 src;
|
|
|
|
if ((index > 1) || !vc5->clk_mux_ins)
|
|
return -EINVAL;
|
|
|
|
if (vc5->clk_mux_ins == (VC5_MUX_IN_CLKIN | VC5_MUX_IN_XIN)) {
|
|
if (index == 0)
|
|
src = VC5_PRIM_SRC_SHDN_EN_XTAL;
|
|
if (index == 1)
|
|
src = VC5_PRIM_SRC_SHDN_EN_CLKIN;
|
|
} else {
|
|
if (index != 0)
|
|
return -EINVAL;
|
|
|
|
if (vc5->clk_mux_ins == VC5_MUX_IN_XIN)
|
|
src = VC5_PRIM_SRC_SHDN_EN_XTAL;
|
|
else if (vc5->clk_mux_ins == VC5_MUX_IN_CLKIN)
|
|
src = VC5_PRIM_SRC_SHDN_EN_CLKIN;
|
|
else /* Invalid; should have been caught by vc5_probe() */
|
|
return -EINVAL;
|
|
}
|
|
|
|
return regmap_update_bits(vc5->regmap, VC5_PRIM_SRC_SHDN, mask, src);
|
|
}
|
|
|
|
static const struct clk_ops vc5_mux_ops = {
|
|
.set_parent = vc5_mux_set_parent,
|
|
.get_parent = vc5_mux_get_parent,
|
|
};
|
|
|
|
static unsigned long vc5_dbl_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct vc5_driver_data *vc5 =
|
|
container_of(hw, struct vc5_driver_data, clk_mul);
|
|
unsigned int premul;
|
|
int ret;
|
|
|
|
ret = regmap_read(vc5->regmap, VC5_PRIM_SRC_SHDN, &premul);
|
|
if (ret)
|
|
return 0;
|
|
|
|
if (premul & VC5_PRIM_SRC_SHDN_EN_DOUBLE_XTAL_FREQ)
|
|
parent_rate *= 2;
|
|
|
|
return parent_rate;
|
|
}
|
|
|
|
static long vc5_dbl_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *parent_rate)
|
|
{
|
|
if ((*parent_rate == rate) || ((*parent_rate * 2) == rate))
|
|
return rate;
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int vc5_dbl_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct vc5_driver_data *vc5 =
|
|
container_of(hw, struct vc5_driver_data, clk_mul);
|
|
u32 mask;
|
|
|
|
if ((parent_rate * 2) == rate)
|
|
mask = VC5_PRIM_SRC_SHDN_EN_DOUBLE_XTAL_FREQ;
|
|
else
|
|
mask = 0;
|
|
|
|
return regmap_update_bits(vc5->regmap, VC5_PRIM_SRC_SHDN,
|
|
VC5_PRIM_SRC_SHDN_EN_DOUBLE_XTAL_FREQ,
|
|
mask);
|
|
}
|
|
|
|
static const struct clk_ops vc5_dbl_ops = {
|
|
.recalc_rate = vc5_dbl_recalc_rate,
|
|
.round_rate = vc5_dbl_round_rate,
|
|
.set_rate = vc5_dbl_set_rate,
|
|
};
|
|
|
|
static unsigned long vc5_pfd_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct vc5_driver_data *vc5 =
|
|
container_of(hw, struct vc5_driver_data, clk_pfd);
|
|
unsigned int prediv, div;
|
|
int ret;
|
|
|
|
ret = regmap_read(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV, &prediv);
|
|
if (ret)
|
|
return 0;
|
|
|
|
/* The bypass_prediv is set, PLL fed from Ref_in directly. */
|
|
if (prediv & VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV)
|
|
return parent_rate;
|
|
|
|
ret = regmap_read(vc5->regmap, VC5_REF_DIVIDER, &div);
|
|
if (ret)
|
|
return 0;
|
|
|
|
/* The Sel_prediv2 is set, PLL fed from prediv2 (Ref_in / 2) */
|
|
if (div & VC5_REF_DIVIDER_SEL_PREDIV2)
|
|
return parent_rate / 2;
|
|
else
|
|
return parent_rate / VC5_REF_DIVIDER_REF_DIV(div);
|
|
}
|
|
|
|
static long vc5_pfd_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *parent_rate)
|
|
{
|
|
unsigned long idiv;
|
|
|
|
/* PLL cannot operate with input clock above 50 MHz. */
|
|
if (rate > 50000000)
|
|
return -EINVAL;
|
|
|
|
/* CLKIN within range of PLL input, feed directly to PLL. */
|
|
if (*parent_rate <= 50000000)
|
|
return *parent_rate;
|
|
|
|
idiv = DIV_ROUND_UP(*parent_rate, rate);
|
|
if (idiv > 127)
|
|
return -EINVAL;
|
|
|
|
return *parent_rate / idiv;
|
|
}
|
|
|
|
static int vc5_pfd_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct vc5_driver_data *vc5 =
|
|
container_of(hw, struct vc5_driver_data, clk_pfd);
|
|
unsigned long idiv;
|
|
int ret;
|
|
u8 div;
|
|
|
|
/* CLKIN within range of PLL input, feed directly to PLL. */
|
|
if (parent_rate <= 50000000) {
|
|
ret = regmap_set_bits(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV,
|
|
VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return regmap_update_bits(vc5->regmap, VC5_REF_DIVIDER, 0xff, 0x00);
|
|
}
|
|
|
|
idiv = DIV_ROUND_UP(parent_rate, rate);
|
|
|
|
/* We have dedicated div-2 predivider. */
|
|
if (idiv == 2)
|
|
div = VC5_REF_DIVIDER_SEL_PREDIV2;
|
|
else
|
|
div = VC5_REF_DIVIDER_REF_DIV(idiv);
|
|
|
|
ret = regmap_update_bits(vc5->regmap, VC5_REF_DIVIDER, 0xff, div);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return regmap_clear_bits(vc5->regmap, VC5_VCO_CTRL_AND_PREDIV,
|
|
VC5_VCO_CTRL_AND_PREDIV_BYPASS_PREDIV);
|
|
}
|
|
|
|
static const struct clk_ops vc5_pfd_ops = {
|
|
.recalc_rate = vc5_pfd_recalc_rate,
|
|
.round_rate = vc5_pfd_round_rate,
|
|
.set_rate = vc5_pfd_set_rate,
|
|
};
|
|
|
|
/*
|
|
* VersaClock5 PLL/VCO
|
|
*/
|
|
static unsigned long vc5_pll_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct vc5_hw_data *hwdata = container_of(hw, struct vc5_hw_data, hw);
|
|
struct vc5_driver_data *vc5 = hwdata->vc5;
|
|
u32 div_int, div_frc;
|
|
u8 fb[5];
|
|
|
|
regmap_bulk_read(vc5->regmap, VC5_FEEDBACK_INT_DIV, fb, 5);
|
|
|
|
div_int = (fb[0] << 4) | (fb[1] >> 4);
|
|
div_frc = (fb[2] << 16) | (fb[3] << 8) | fb[4];
|
|
|
|
/* The PLL divider has 12 integer bits and 24 fractional bits */
|
|
return (parent_rate * div_int) + ((parent_rate * div_frc) >> 24);
|
|
}
|
|
|
|
static long vc5_pll_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *parent_rate)
|
|
{
|
|
struct vc5_hw_data *hwdata = container_of(hw, struct vc5_hw_data, hw);
|
|
u32 div_int;
|
|
u64 div_frc;
|
|
|
|
if (rate < VC5_PLL_VCO_MIN)
|
|
rate = VC5_PLL_VCO_MIN;
|
|
if (rate > VC5_PLL_VCO_MAX)
|
|
rate = VC5_PLL_VCO_MAX;
|
|
|
|
/* Determine integer part, which is 12 bit wide */
|
|
div_int = rate / *parent_rate;
|
|
if (div_int > 0xfff)
|
|
rate = *parent_rate * 0xfff;
|
|
|
|
/* Determine best fractional part, which is 24 bit wide */
|
|
div_frc = rate % *parent_rate;
|
|
div_frc *= BIT(24) - 1;
|
|
do_div(div_frc, *parent_rate);
|
|
|
|
hwdata->div_int = div_int;
|
|
hwdata->div_frc = (u32)div_frc;
|
|
|
|
return (*parent_rate * div_int) + ((*parent_rate * div_frc) >> 24);
|
|
}
|
|
|
|
static int vc5_pll_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct vc5_hw_data *hwdata = container_of(hw, struct vc5_hw_data, hw);
|
|
struct vc5_driver_data *vc5 = hwdata->vc5;
|
|
u8 fb[5];
|
|
|
|
fb[0] = hwdata->div_int >> 4;
|
|
fb[1] = hwdata->div_int << 4;
|
|
fb[2] = hwdata->div_frc >> 16;
|
|
fb[3] = hwdata->div_frc >> 8;
|
|
fb[4] = hwdata->div_frc;
|
|
|
|
return regmap_bulk_write(vc5->regmap, VC5_FEEDBACK_INT_DIV, fb, 5);
|
|
}
|
|
|
|
static const struct clk_ops vc5_pll_ops = {
|
|
.recalc_rate = vc5_pll_recalc_rate,
|
|
.round_rate = vc5_pll_round_rate,
|
|
.set_rate = vc5_pll_set_rate,
|
|
};
|
|
|
|
static unsigned long vc5_fod_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct vc5_hw_data *hwdata = container_of(hw, struct vc5_hw_data, hw);
|
|
struct vc5_driver_data *vc5 = hwdata->vc5;
|
|
/* VCO frequency is divided by two before entering FOD */
|
|
u32 f_in = parent_rate / 2;
|
|
u32 div_int, div_frc;
|
|
u8 od_int[2];
|
|
u8 od_frc[4];
|
|
|
|
regmap_bulk_read(vc5->regmap, VC5_OUT_DIV_INT(hwdata->num, 0),
|
|
od_int, 2);
|
|
regmap_bulk_read(vc5->regmap, VC5_OUT_DIV_FRAC(hwdata->num, 0),
|
|
od_frc, 4);
|
|
|
|
div_int = (od_int[0] << 4) | (od_int[1] >> 4);
|
|
div_frc = (od_frc[0] << 22) | (od_frc[1] << 14) |
|
|
(od_frc[2] << 6) | (od_frc[3] >> 2);
|
|
|
|
/* Avoid division by zero if the output is not configured. */
|
|
if (div_int == 0 && div_frc == 0)
|
|
return 0;
|
|
|
|
/* The PLL divider has 12 integer bits and 30 fractional bits */
|
|
return div64_u64((u64)f_in << 24ULL, ((u64)div_int << 24ULL) + div_frc);
|
|
}
|
|
|
|
static long vc5_fod_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *parent_rate)
|
|
{
|
|
struct vc5_hw_data *hwdata = container_of(hw, struct vc5_hw_data, hw);
|
|
/* VCO frequency is divided by two before entering FOD */
|
|
u32 f_in = *parent_rate / 2;
|
|
u32 div_int;
|
|
u64 div_frc;
|
|
|
|
/* Determine integer part, which is 12 bit wide */
|
|
div_int = f_in / rate;
|
|
/*
|
|
* WARNING: The clock chip does not output signal if the integer part
|
|
* of the divider is 0xfff and fractional part is non-zero.
|
|
* Clamp the divider at 0xffe to keep the code simple.
|
|
*/
|
|
if (div_int > 0xffe) {
|
|
div_int = 0xffe;
|
|
rate = f_in / div_int;
|
|
}
|
|
|
|
/* Determine best fractional part, which is 30 bit wide */
|
|
div_frc = f_in % rate;
|
|
div_frc <<= 24;
|
|
do_div(div_frc, rate);
|
|
|
|
hwdata->div_int = div_int;
|
|
hwdata->div_frc = (u32)div_frc;
|
|
|
|
return div64_u64((u64)f_in << 24ULL, ((u64)div_int << 24ULL) + div_frc);
|
|
}
|
|
|
|
static int vc5_fod_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct vc5_hw_data *hwdata = container_of(hw, struct vc5_hw_data, hw);
|
|
struct vc5_driver_data *vc5 = hwdata->vc5;
|
|
u8 data[14] = {
|
|
hwdata->div_frc >> 22, hwdata->div_frc >> 14,
|
|
hwdata->div_frc >> 6, hwdata->div_frc << 2,
|
|
0, 0, 0, 0, 0,
|
|
0, 0,
|
|
hwdata->div_int >> 4, hwdata->div_int << 4,
|
|
0
|
|
};
|
|
int ret;
|
|
|
|
ret = regmap_bulk_write(vc5->regmap, VC5_OUT_DIV_FRAC(hwdata->num, 0),
|
|
data, 14);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Toggle magic bit in undocumented register for unknown reason.
|
|
* This is what the IDT timing commander tool does and the chip
|
|
* datasheet somewhat implies this is needed, but the register
|
|
* and the bit is not documented.
|
|
*/
|
|
ret = regmap_clear_bits(vc5->regmap, VC5_GLOBAL_REGISTER,
|
|
VC5_GLOBAL_REGISTER_GLOBAL_RESET);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return regmap_set_bits(vc5->regmap, VC5_GLOBAL_REGISTER,
|
|
VC5_GLOBAL_REGISTER_GLOBAL_RESET);
|
|
}
|
|
|
|
static const struct clk_ops vc5_fod_ops = {
|
|
.recalc_rate = vc5_fod_recalc_rate,
|
|
.round_rate = vc5_fod_round_rate,
|
|
.set_rate = vc5_fod_set_rate,
|
|
};
|
|
|
|
static int vc5_clk_out_prepare(struct clk_hw *hw)
|
|
{
|
|
struct vc5_out_data *hwdata = container_of(hw, struct vc5_out_data, hw);
|
|
struct vc5_driver_data *vc5 = hwdata->vc5;
|
|
const u8 mask = VC5_OUT_DIV_CONTROL_SELB_NORM |
|
|
VC5_OUT_DIV_CONTROL_SEL_EXT |
|
|
VC5_OUT_DIV_CONTROL_EN_FOD;
|
|
unsigned int src;
|
|
int ret;
|
|
|
|
/*
|
|
* When enabling a FOD, all currently enabled FODs are briefly
|
|
* stopped in order to synchronize all of them. This causes a clock
|
|
* disruption to any unrelated chips that might be already using
|
|
* other clock outputs. Bypass the sync feature to avoid the issue,
|
|
* which is possible on the VersaClock 6E family via reserved
|
|
* registers.
|
|
*/
|
|
if (vc5->chip_info->flags & VC5_HAS_BYPASS_SYNC_BIT) {
|
|
ret = regmap_set_bits(vc5->regmap,
|
|
VC5_RESERVED_X0(hwdata->num),
|
|
VC5_RESERVED_X0_BYPASS_SYNC);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* If the input mux is disabled, enable it first and
|
|
* select source from matching FOD.
|
|
*/
|
|
ret = regmap_read(vc5->regmap, VC5_OUT_DIV_CONTROL(hwdata->num), &src);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if ((src & mask) == 0) {
|
|
src = VC5_OUT_DIV_CONTROL_RESET | VC5_OUT_DIV_CONTROL_EN_FOD;
|
|
ret = regmap_update_bits(vc5->regmap,
|
|
VC5_OUT_DIV_CONTROL(hwdata->num),
|
|
mask | VC5_OUT_DIV_CONTROL_RESET, src);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/* Enable the clock buffer */
|
|
ret = regmap_set_bits(vc5->regmap, VC5_CLK_OUTPUT_CFG(hwdata->num, 1),
|
|
VC5_CLK_OUTPUT_CFG1_EN_CLKBUF);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (hwdata->clk_output_cfg0_mask) {
|
|
dev_dbg(&vc5->client->dev, "Update output %d mask 0x%0X val 0x%0X\n",
|
|
hwdata->num, hwdata->clk_output_cfg0_mask,
|
|
hwdata->clk_output_cfg0);
|
|
|
|
ret = regmap_update_bits(vc5->regmap,
|
|
VC5_CLK_OUTPUT_CFG(hwdata->num, 0),
|
|
hwdata->clk_output_cfg0_mask,
|
|
hwdata->clk_output_cfg0);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void vc5_clk_out_unprepare(struct clk_hw *hw)
|
|
{
|
|
struct vc5_out_data *hwdata = container_of(hw, struct vc5_out_data, hw);
|
|
struct vc5_driver_data *vc5 = hwdata->vc5;
|
|
|
|
/* Disable the clock buffer */
|
|
regmap_clear_bits(vc5->regmap, VC5_CLK_OUTPUT_CFG(hwdata->num, 1),
|
|
VC5_CLK_OUTPUT_CFG1_EN_CLKBUF);
|
|
}
|
|
|
|
static unsigned char vc5_clk_out_get_parent(struct clk_hw *hw)
|
|
{
|
|
struct vc5_out_data *hwdata = container_of(hw, struct vc5_out_data, hw);
|
|
struct vc5_driver_data *vc5 = hwdata->vc5;
|
|
const u8 mask = VC5_OUT_DIV_CONTROL_SELB_NORM |
|
|
VC5_OUT_DIV_CONTROL_SEL_EXT |
|
|
VC5_OUT_DIV_CONTROL_EN_FOD;
|
|
const u8 fodclkmask = VC5_OUT_DIV_CONTROL_SELB_NORM |
|
|
VC5_OUT_DIV_CONTROL_EN_FOD;
|
|
const u8 extclk = VC5_OUT_DIV_CONTROL_SELB_NORM |
|
|
VC5_OUT_DIV_CONTROL_SEL_EXT;
|
|
unsigned int src;
|
|
int ret;
|
|
|
|
ret = regmap_read(vc5->regmap, VC5_OUT_DIV_CONTROL(hwdata->num), &src);
|
|
if (ret)
|
|
return 0;
|
|
|
|
src &= mask;
|
|
|
|
if (src == 0) /* Input mux set to DISABLED */
|
|
return 0;
|
|
|
|
if ((src & fodclkmask) == VC5_OUT_DIV_CONTROL_EN_FOD)
|
|
return 0;
|
|
|
|
if (src == extclk)
|
|
return 1;
|
|
|
|
dev_warn(&vc5->client->dev,
|
|
"Invalid clock output configuration (%02x)\n", src);
|
|
return 0;
|
|
}
|
|
|
|
static int vc5_clk_out_set_parent(struct clk_hw *hw, u8 index)
|
|
{
|
|
struct vc5_out_data *hwdata = container_of(hw, struct vc5_out_data, hw);
|
|
struct vc5_driver_data *vc5 = hwdata->vc5;
|
|
const u8 mask = VC5_OUT_DIV_CONTROL_RESET |
|
|
VC5_OUT_DIV_CONTROL_SELB_NORM |
|
|
VC5_OUT_DIV_CONTROL_SEL_EXT |
|
|
VC5_OUT_DIV_CONTROL_EN_FOD;
|
|
const u8 extclk = VC5_OUT_DIV_CONTROL_SELB_NORM |
|
|
VC5_OUT_DIV_CONTROL_SEL_EXT;
|
|
u8 src = VC5_OUT_DIV_CONTROL_RESET;
|
|
|
|
if (index == 0)
|
|
src |= VC5_OUT_DIV_CONTROL_EN_FOD;
|
|
else
|
|
src |= extclk;
|
|
|
|
return regmap_update_bits(vc5->regmap, VC5_OUT_DIV_CONTROL(hwdata->num),
|
|
mask, src);
|
|
}
|
|
|
|
static const struct clk_ops vc5_clk_out_ops = {
|
|
.prepare = vc5_clk_out_prepare,
|
|
.unprepare = vc5_clk_out_unprepare,
|
|
.set_parent = vc5_clk_out_set_parent,
|
|
.get_parent = vc5_clk_out_get_parent,
|
|
};
|
|
|
|
static struct clk_hw *vc5_of_clk_get(struct of_phandle_args *clkspec,
|
|
void *data)
|
|
{
|
|
struct vc5_driver_data *vc5 = data;
|
|
unsigned int idx = clkspec->args[0];
|
|
|
|
if (idx >= vc5->chip_info->clk_out_cnt)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
return &vc5->clk_out[idx].hw;
|
|
}
|
|
|
|
static int vc5_map_index_to_output(const enum vc5_model model,
|
|
const unsigned int n)
|
|
{
|
|
switch (model) {
|
|
case IDT_VC5_5P49V5933:
|
|
return (n == 0) ? 0 : 3;
|
|
case IDT_VC5_5P49V5923:
|
|
case IDT_VC5_5P49V5925:
|
|
case IDT_VC5_5P49V5935:
|
|
case IDT_VC6_5P49V6901:
|
|
case IDT_VC6_5P49V6965:
|
|
case IDT_VC6_5P49V6975:
|
|
default:
|
|
return n;
|
|
}
|
|
}
|
|
|
|
static int vc5_update_mode(struct device_node *np_output,
|
|
struct vc5_out_data *clk_out)
|
|
{
|
|
u32 value;
|
|
|
|
if (!of_property_read_u32(np_output, "idt,mode", &value)) {
|
|
clk_out->clk_output_cfg0_mask |= VC5_CLK_OUTPUT_CFG0_CFG_MASK;
|
|
switch (value) {
|
|
case VC5_CLK_OUTPUT_CFG0_CFG_LVPECL:
|
|
case VC5_CLK_OUTPUT_CFG0_CFG_CMOS:
|
|
case VC5_CLK_OUTPUT_CFG0_CFG_HCSL33:
|
|
case VC5_CLK_OUTPUT_CFG0_CFG_LVDS:
|
|
case VC5_CLK_OUTPUT_CFG0_CFG_CMOS2:
|
|
case VC5_CLK_OUTPUT_CFG0_CFG_CMOSD:
|
|
case VC5_CLK_OUTPUT_CFG0_CFG_HCSL25:
|
|
clk_out->clk_output_cfg0 |=
|
|
value << VC5_CLK_OUTPUT_CFG0_CFG_SHIFT;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int vc5_update_power(struct device_node *np_output,
|
|
struct vc5_out_data *clk_out)
|
|
{
|
|
u32 value;
|
|
|
|
if (!of_property_read_u32(np_output, "idt,voltage-microvolt",
|
|
&value)) {
|
|
clk_out->clk_output_cfg0_mask |= VC5_CLK_OUTPUT_CFG0_PWR_MASK;
|
|
switch (value) {
|
|
case 1800000:
|
|
clk_out->clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_PWR_18;
|
|
break;
|
|
case 2500000:
|
|
clk_out->clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_PWR_25;
|
|
break;
|
|
case 3300000:
|
|
clk_out->clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_PWR_33;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int vc5_map_cap_value(u32 femtofarads)
|
|
{
|
|
int mapped_value;
|
|
|
|
/*
|
|
* The datasheet explicitly states 9000 - 25000 with 0.5pF
|
|
* steps, but the Programmer's guide shows the steps are 0.430pF.
|
|
* After getting feedback from Renesas, the .5pF steps were the
|
|
* goal, but 430nF was the actual values.
|
|
* Because of this, the actual range goes to 22760 instead of 25000
|
|
*/
|
|
if (femtofarads < 9000 || femtofarads > 22760)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* The Programmer's guide shows XTAL[5:0] but in reality,
|
|
* XTAL[0] and XTAL[1] are both LSB which makes the math
|
|
* strange. With clarfication from Renesas, setting the
|
|
* values should be simpler by ignoring XTAL[0]
|
|
*/
|
|
mapped_value = DIV_ROUND_CLOSEST(femtofarads - 9000, 430);
|
|
|
|
/*
|
|
* Since the calculation ignores XTAL[0], there is one
|
|
* special case where mapped_value = 32. In reality, this means
|
|
* the real mapped value should be 111111b. In other cases,
|
|
* the mapped_value needs to be shifted 1 to the left.
|
|
*/
|
|
if (mapped_value > 31)
|
|
mapped_value = 0x3f;
|
|
else
|
|
mapped_value <<= 1;
|
|
|
|
return mapped_value;
|
|
}
|
|
static int vc5_update_cap_load(struct device_node *node, struct vc5_driver_data *vc5)
|
|
{
|
|
u32 value;
|
|
int mapped_value;
|
|
int ret;
|
|
|
|
if (of_property_read_u32(node, "idt,xtal-load-femtofarads", &value))
|
|
return 0;
|
|
|
|
mapped_value = vc5_map_cap_value(value);
|
|
if (mapped_value < 0)
|
|
return mapped_value;
|
|
|
|
/*
|
|
* The mapped_value is really the high 6 bits of
|
|
* VC5_XTAL_X1_LOAD_CAP and VC5_XTAL_X2_LOAD_CAP, so
|
|
* shift the value 2 places.
|
|
*/
|
|
ret = regmap_update_bits(vc5->regmap, VC5_XTAL_X1_LOAD_CAP, ~0x03,
|
|
mapped_value << 2);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return regmap_update_bits(vc5->regmap, VC5_XTAL_X2_LOAD_CAP, ~0x03,
|
|
mapped_value << 2);
|
|
}
|
|
|
|
static int vc5_update_slew(struct device_node *np_output,
|
|
struct vc5_out_data *clk_out)
|
|
{
|
|
u32 value;
|
|
|
|
if (!of_property_read_u32(np_output, "idt,slew-percent", &value)) {
|
|
clk_out->clk_output_cfg0_mask |= VC5_CLK_OUTPUT_CFG0_SLEW_MASK;
|
|
switch (value) {
|
|
case 80:
|
|
clk_out->clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_SLEW_80;
|
|
break;
|
|
case 85:
|
|
clk_out->clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_SLEW_85;
|
|
break;
|
|
case 90:
|
|
clk_out->clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_SLEW_90;
|
|
break;
|
|
case 100:
|
|
clk_out->clk_output_cfg0 |=
|
|
VC5_CLK_OUTPUT_CFG0_SLEW_100;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int vc5_get_output_config(struct i2c_client *client,
|
|
struct vc5_out_data *clk_out)
|
|
{
|
|
struct device_node *np_output;
|
|
char *child_name;
|
|
int ret = 0;
|
|
|
|
child_name = kasprintf(GFP_KERNEL, "OUT%d", clk_out->num + 1);
|
|
if (!child_name)
|
|
return -ENOMEM;
|
|
|
|
np_output = of_get_child_by_name(client->dev.of_node, child_name);
|
|
kfree(child_name);
|
|
if (!np_output)
|
|
return 0;
|
|
|
|
ret = vc5_update_mode(np_output, clk_out);
|
|
if (ret)
|
|
goto output_error;
|
|
|
|
ret = vc5_update_power(np_output, clk_out);
|
|
if (ret)
|
|
goto output_error;
|
|
|
|
ret = vc5_update_slew(np_output, clk_out);
|
|
|
|
output_error:
|
|
if (ret) {
|
|
dev_err(&client->dev,
|
|
"Invalid clock output configuration OUT%d\n",
|
|
clk_out->num + 1);
|
|
}
|
|
|
|
of_node_put(np_output);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct of_device_id clk_vc5_of_match[];
|
|
|
|
static int vc5_probe(struct i2c_client *client)
|
|
{
|
|
unsigned int oe, sd, src_mask = 0, src_val = 0;
|
|
struct vc5_driver_data *vc5;
|
|
struct clk_init_data init;
|
|
const char *parent_names[2];
|
|
unsigned int n, idx = 0;
|
|
int ret;
|
|
|
|
vc5 = devm_kzalloc(&client->dev, sizeof(*vc5), GFP_KERNEL);
|
|
if (!vc5)
|
|
return -ENOMEM;
|
|
|
|
i2c_set_clientdata(client, vc5);
|
|
vc5->client = client;
|
|
vc5->chip_info = of_device_get_match_data(&client->dev);
|
|
|
|
vc5->pin_xin = devm_clk_get(&client->dev, "xin");
|
|
if (PTR_ERR(vc5->pin_xin) == -EPROBE_DEFER)
|
|
return -EPROBE_DEFER;
|
|
|
|
vc5->pin_clkin = devm_clk_get(&client->dev, "clkin");
|
|
if (PTR_ERR(vc5->pin_clkin) == -EPROBE_DEFER)
|
|
return -EPROBE_DEFER;
|
|
|
|
vc5->regmap = devm_regmap_init_i2c(client, &vc5_regmap_config);
|
|
if (IS_ERR(vc5->regmap))
|
|
return dev_err_probe(&client->dev, PTR_ERR(vc5->regmap),
|
|
"failed to allocate register map\n");
|
|
|
|
ret = of_property_read_u32(client->dev.of_node, "idt,shutdown", &sd);
|
|
if (!ret) {
|
|
src_mask |= VC5_PRIM_SRC_SHDN_EN_GBL_SHDN;
|
|
if (sd)
|
|
src_val |= VC5_PRIM_SRC_SHDN_EN_GBL_SHDN;
|
|
} else if (ret != -EINVAL) {
|
|
return dev_err_probe(&client->dev, ret,
|
|
"could not read idt,shutdown\n");
|
|
}
|
|
|
|
ret = of_property_read_u32(client->dev.of_node,
|
|
"idt,output-enable-active", &oe);
|
|
if (!ret) {
|
|
src_mask |= VC5_PRIM_SRC_SHDN_SP;
|
|
if (oe)
|
|
src_val |= VC5_PRIM_SRC_SHDN_SP;
|
|
} else if (ret != -EINVAL) {
|
|
return dev_err_probe(&client->dev, ret,
|
|
"could not read idt,output-enable-active\n");
|
|
}
|
|
|
|
ret = regmap_update_bits(vc5->regmap, VC5_PRIM_SRC_SHDN, src_mask,
|
|
src_val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Register clock input mux */
|
|
memset(&init, 0, sizeof(init));
|
|
|
|
if (!IS_ERR(vc5->pin_xin)) {
|
|
vc5->clk_mux_ins |= VC5_MUX_IN_XIN;
|
|
parent_names[init.num_parents++] = __clk_get_name(vc5->pin_xin);
|
|
} else if (vc5->chip_info->flags & VC5_HAS_INTERNAL_XTAL) {
|
|
vc5->pin_xin = clk_register_fixed_rate(&client->dev,
|
|
"internal-xtal", NULL,
|
|
0, 25000000);
|
|
if (IS_ERR(vc5->pin_xin))
|
|
return PTR_ERR(vc5->pin_xin);
|
|
vc5->clk_mux_ins |= VC5_MUX_IN_XIN;
|
|
parent_names[init.num_parents++] = __clk_get_name(vc5->pin_xin);
|
|
}
|
|
|
|
if (!IS_ERR(vc5->pin_clkin)) {
|
|
vc5->clk_mux_ins |= VC5_MUX_IN_CLKIN;
|
|
parent_names[init.num_parents++] =
|
|
__clk_get_name(vc5->pin_clkin);
|
|
}
|
|
|
|
if (!init.num_parents)
|
|
return dev_err_probe(&client->dev, -EINVAL,
|
|
"no input clock specified!\n");
|
|
|
|
/* Configure Optional Loading Capacitance for external XTAL */
|
|
if (!(vc5->chip_info->flags & VC5_HAS_INTERNAL_XTAL)) {
|
|
ret = vc5_update_cap_load(client->dev.of_node, vc5);
|
|
if (ret)
|
|
goto err_clk_register;
|
|
}
|
|
|
|
init.name = kasprintf(GFP_KERNEL, "%pOFn.mux", client->dev.of_node);
|
|
init.ops = &vc5_mux_ops;
|
|
init.flags = 0;
|
|
init.parent_names = parent_names;
|
|
vc5->clk_mux.init = &init;
|
|
ret = devm_clk_hw_register(&client->dev, &vc5->clk_mux);
|
|
if (ret)
|
|
goto err_clk_register;
|
|
kfree(init.name); /* clock framework made a copy of the name */
|
|
|
|
if (vc5->chip_info->flags & VC5_HAS_PFD_FREQ_DBL) {
|
|
/* Register frequency doubler */
|
|
memset(&init, 0, sizeof(init));
|
|
init.name = kasprintf(GFP_KERNEL, "%pOFn.dbl",
|
|
client->dev.of_node);
|
|
init.ops = &vc5_dbl_ops;
|
|
init.flags = CLK_SET_RATE_PARENT;
|
|
init.parent_names = parent_names;
|
|
parent_names[0] = clk_hw_get_name(&vc5->clk_mux);
|
|
init.num_parents = 1;
|
|
vc5->clk_mul.init = &init;
|
|
ret = devm_clk_hw_register(&client->dev, &vc5->clk_mul);
|
|
if (ret)
|
|
goto err_clk_register;
|
|
kfree(init.name); /* clock framework made a copy of the name */
|
|
}
|
|
|
|
/* Register PFD */
|
|
memset(&init, 0, sizeof(init));
|
|
init.name = kasprintf(GFP_KERNEL, "%pOFn.pfd", client->dev.of_node);
|
|
init.ops = &vc5_pfd_ops;
|
|
init.flags = CLK_SET_RATE_PARENT;
|
|
init.parent_names = parent_names;
|
|
if (vc5->chip_info->flags & VC5_HAS_PFD_FREQ_DBL)
|
|
parent_names[0] = clk_hw_get_name(&vc5->clk_mul);
|
|
else
|
|
parent_names[0] = clk_hw_get_name(&vc5->clk_mux);
|
|
init.num_parents = 1;
|
|
vc5->clk_pfd.init = &init;
|
|
ret = devm_clk_hw_register(&client->dev, &vc5->clk_pfd);
|
|
if (ret)
|
|
goto err_clk_register;
|
|
kfree(init.name); /* clock framework made a copy of the name */
|
|
|
|
/* Register PLL */
|
|
memset(&init, 0, sizeof(init));
|
|
init.name = kasprintf(GFP_KERNEL, "%pOFn.pll", client->dev.of_node);
|
|
init.ops = &vc5_pll_ops;
|
|
init.flags = CLK_SET_RATE_PARENT;
|
|
init.parent_names = parent_names;
|
|
parent_names[0] = clk_hw_get_name(&vc5->clk_pfd);
|
|
init.num_parents = 1;
|
|
vc5->clk_pll.num = 0;
|
|
vc5->clk_pll.vc5 = vc5;
|
|
vc5->clk_pll.hw.init = &init;
|
|
ret = devm_clk_hw_register(&client->dev, &vc5->clk_pll.hw);
|
|
if (ret)
|
|
goto err_clk_register;
|
|
kfree(init.name); /* clock framework made a copy of the name */
|
|
|
|
/* Register FODs */
|
|
for (n = 0; n < vc5->chip_info->clk_fod_cnt; n++) {
|
|
idx = vc5_map_index_to_output(vc5->chip_info->model, n);
|
|
memset(&init, 0, sizeof(init));
|
|
init.name = kasprintf(GFP_KERNEL, "%pOFn.fod%d",
|
|
client->dev.of_node, idx);
|
|
init.ops = &vc5_fod_ops;
|
|
init.flags = CLK_SET_RATE_PARENT;
|
|
init.parent_names = parent_names;
|
|
parent_names[0] = clk_hw_get_name(&vc5->clk_pll.hw);
|
|
init.num_parents = 1;
|
|
vc5->clk_fod[n].num = idx;
|
|
vc5->clk_fod[n].vc5 = vc5;
|
|
vc5->clk_fod[n].hw.init = &init;
|
|
ret = devm_clk_hw_register(&client->dev, &vc5->clk_fod[n].hw);
|
|
if (ret)
|
|
goto err_clk_register;
|
|
kfree(init.name); /* clock framework made a copy of the name */
|
|
}
|
|
|
|
/* Register MUX-connected OUT0_I2C_SELB output */
|
|
memset(&init, 0, sizeof(init));
|
|
init.name = kasprintf(GFP_KERNEL, "%pOFn.out0_sel_i2cb",
|
|
client->dev.of_node);
|
|
init.ops = &vc5_clk_out_ops;
|
|
init.flags = CLK_SET_RATE_PARENT;
|
|
init.parent_names = parent_names;
|
|
parent_names[0] = clk_hw_get_name(&vc5->clk_mux);
|
|
init.num_parents = 1;
|
|
vc5->clk_out[0].num = idx;
|
|
vc5->clk_out[0].vc5 = vc5;
|
|
vc5->clk_out[0].hw.init = &init;
|
|
ret = devm_clk_hw_register(&client->dev, &vc5->clk_out[0].hw);
|
|
if (ret)
|
|
goto err_clk_register;
|
|
kfree(init.name); /* clock framework made a copy of the name */
|
|
|
|
/* Register FOD-connected OUTx outputs */
|
|
for (n = 1; n < vc5->chip_info->clk_out_cnt; n++) {
|
|
idx = vc5_map_index_to_output(vc5->chip_info->model, n - 1);
|
|
parent_names[0] = clk_hw_get_name(&vc5->clk_fod[idx].hw);
|
|
if (n == 1)
|
|
parent_names[1] = clk_hw_get_name(&vc5->clk_mux);
|
|
else
|
|
parent_names[1] =
|
|
clk_hw_get_name(&vc5->clk_out[n - 1].hw);
|
|
|
|
memset(&init, 0, sizeof(init));
|
|
init.name = kasprintf(GFP_KERNEL, "%pOFn.out%d",
|
|
client->dev.of_node, idx + 1);
|
|
init.ops = &vc5_clk_out_ops;
|
|
init.flags = CLK_SET_RATE_PARENT;
|
|
init.parent_names = parent_names;
|
|
init.num_parents = 2;
|
|
vc5->clk_out[n].num = idx;
|
|
vc5->clk_out[n].vc5 = vc5;
|
|
vc5->clk_out[n].hw.init = &init;
|
|
ret = devm_clk_hw_register(&client->dev, &vc5->clk_out[n].hw);
|
|
if (ret)
|
|
goto err_clk_register;
|
|
kfree(init.name); /* clock framework made a copy of the name */
|
|
|
|
/* Fetch Clock Output configuration from DT (if specified) */
|
|
ret = vc5_get_output_config(client, &vc5->clk_out[n]);
|
|
if (ret)
|
|
goto err_clk;
|
|
}
|
|
|
|
ret = of_clk_add_hw_provider(client->dev.of_node, vc5_of_clk_get, vc5);
|
|
if (ret) {
|
|
dev_err_probe(&client->dev, ret,
|
|
"unable to add clk provider\n");
|
|
goto err_clk;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_clk_register:
|
|
dev_err_probe(&client->dev, ret,
|
|
"unable to register %s\n", init.name);
|
|
kfree(init.name); /* clock framework made a copy of the name */
|
|
err_clk:
|
|
if (vc5->chip_info->flags & VC5_HAS_INTERNAL_XTAL)
|
|
clk_unregister_fixed_rate(vc5->pin_xin);
|
|
return ret;
|
|
}
|
|
|
|
static void vc5_remove(struct i2c_client *client)
|
|
{
|
|
struct vc5_driver_data *vc5 = i2c_get_clientdata(client);
|
|
|
|
of_clk_del_provider(client->dev.of_node);
|
|
|
|
if (vc5->chip_info->flags & VC5_HAS_INTERNAL_XTAL)
|
|
clk_unregister_fixed_rate(vc5->pin_xin);
|
|
}
|
|
|
|
static int __maybe_unused vc5_suspend(struct device *dev)
|
|
{
|
|
struct vc5_driver_data *vc5 = dev_get_drvdata(dev);
|
|
|
|
regcache_cache_only(vc5->regmap, true);
|
|
regcache_mark_dirty(vc5->regmap);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused vc5_resume(struct device *dev)
|
|
{
|
|
struct vc5_driver_data *vc5 = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
regcache_cache_only(vc5->regmap, false);
|
|
ret = regcache_sync(vc5->regmap);
|
|
if (ret)
|
|
dev_err(dev, "Failed to restore register map: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static const struct vc5_chip_info idt_5p49v5923_info = {
|
|
.model = IDT_VC5_5P49V5923,
|
|
.clk_fod_cnt = 2,
|
|
.clk_out_cnt = 3,
|
|
.flags = 0,
|
|
};
|
|
|
|
static const struct vc5_chip_info idt_5p49v5925_info = {
|
|
.model = IDT_VC5_5P49V5925,
|
|
.clk_fod_cnt = 4,
|
|
.clk_out_cnt = 5,
|
|
.flags = 0,
|
|
};
|
|
|
|
static const struct vc5_chip_info idt_5p49v5933_info = {
|
|
.model = IDT_VC5_5P49V5933,
|
|
.clk_fod_cnt = 2,
|
|
.clk_out_cnt = 3,
|
|
.flags = VC5_HAS_INTERNAL_XTAL,
|
|
};
|
|
|
|
static const struct vc5_chip_info idt_5p49v5935_info = {
|
|
.model = IDT_VC5_5P49V5935,
|
|
.clk_fod_cnt = 4,
|
|
.clk_out_cnt = 5,
|
|
.flags = VC5_HAS_INTERNAL_XTAL,
|
|
};
|
|
|
|
static const struct vc5_chip_info idt_5p49v6901_info = {
|
|
.model = IDT_VC6_5P49V6901,
|
|
.clk_fod_cnt = 4,
|
|
.clk_out_cnt = 5,
|
|
.flags = VC5_HAS_PFD_FREQ_DBL | VC5_HAS_BYPASS_SYNC_BIT,
|
|
};
|
|
|
|
static const struct vc5_chip_info idt_5p49v6965_info = {
|
|
.model = IDT_VC6_5P49V6965,
|
|
.clk_fod_cnt = 4,
|
|
.clk_out_cnt = 5,
|
|
.flags = VC5_HAS_BYPASS_SYNC_BIT,
|
|
};
|
|
|
|
static const struct vc5_chip_info idt_5p49v6975_info = {
|
|
.model = IDT_VC6_5P49V6975,
|
|
.clk_fod_cnt = 4,
|
|
.clk_out_cnt = 5,
|
|
.flags = VC5_HAS_BYPASS_SYNC_BIT | VC5_HAS_INTERNAL_XTAL,
|
|
};
|
|
|
|
static const struct i2c_device_id vc5_id[] = {
|
|
{ "5p49v5923", .driver_data = IDT_VC5_5P49V5923 },
|
|
{ "5p49v5925", .driver_data = IDT_VC5_5P49V5925 },
|
|
{ "5p49v5933", .driver_data = IDT_VC5_5P49V5933 },
|
|
{ "5p49v5935", .driver_data = IDT_VC5_5P49V5935 },
|
|
{ "5p49v6901", .driver_data = IDT_VC6_5P49V6901 },
|
|
{ "5p49v6965", .driver_data = IDT_VC6_5P49V6965 },
|
|
{ "5p49v6975", .driver_data = IDT_VC6_5P49V6975 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, vc5_id);
|
|
|
|
static const struct of_device_id clk_vc5_of_match[] = {
|
|
{ .compatible = "idt,5p49v5923", .data = &idt_5p49v5923_info },
|
|
{ .compatible = "idt,5p49v5925", .data = &idt_5p49v5925_info },
|
|
{ .compatible = "idt,5p49v5933", .data = &idt_5p49v5933_info },
|
|
{ .compatible = "idt,5p49v5935", .data = &idt_5p49v5935_info },
|
|
{ .compatible = "idt,5p49v6901", .data = &idt_5p49v6901_info },
|
|
{ .compatible = "idt,5p49v6965", .data = &idt_5p49v6965_info },
|
|
{ .compatible = "idt,5p49v6975", .data = &idt_5p49v6975_info },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, clk_vc5_of_match);
|
|
|
|
static SIMPLE_DEV_PM_OPS(vc5_pm_ops, vc5_suspend, vc5_resume);
|
|
|
|
static struct i2c_driver vc5_driver = {
|
|
.driver = {
|
|
.name = "vc5",
|
|
.pm = &vc5_pm_ops,
|
|
.of_match_table = clk_vc5_of_match,
|
|
},
|
|
.probe_new = vc5_probe,
|
|
.remove = vc5_remove,
|
|
.id_table = vc5_id,
|
|
};
|
|
module_i2c_driver(vc5_driver);
|
|
|
|
MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
|
|
MODULE_DESCRIPTION("IDT VersaClock 5 driver");
|
|
MODULE_LICENSE("GPL");
|