[PATCH v2 2/2] clk: rockchip: Add support for the mmc clock phases using the framework
The drive and sample phases are generated by dividing an upstream parent clock by 2, this allows us to adjust the phase by 90 deg. There's also an option to have up to 255 delay elements (40-80 picoseconds long). This driver uses those elements (under the assumption that they're 60ps long) to generate a rough 45deg (which might be from 33deg to 66deg) setting. Suggested-by: Heiko Stuebner Signed-off-by: Alexandru M Stan --- Changes in v2: - fixed my cc/to list - removed dangling #DEFINE DEBUG drivers/clk/rockchip/Makefile| 1 + drivers/clk/rockchip/clk-mmc-phase.c | 149 +++ drivers/clk/rockchip/clk-rk3288.c| 12 +++ drivers/clk/rockchip/clk.c | 8 ++ drivers/clk/rockchip/clk.h | 23 ++ 5 files changed, 193 insertions(+) create mode 100644 drivers/clk/rockchip/clk-mmc-phase.c diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index bd8514d..2714097 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -6,6 +6,7 @@ obj-y += clk-rockchip.o obj-y += clk.o obj-y += clk-pll.o obj-y += clk-cpu.o +obj-y += clk-mmc-phase.o obj-$(CONFIG_RESET_CONTROLLER) += softrst.o obj-y += clk-rk3188.o diff --git a/drivers/clk/rockchip/clk-mmc-phase.c b/drivers/clk/rockchip/clk-mmc-phase.c new file mode 100644 index 000..763df4b --- /dev/null +++ b/drivers/clk/rockchip/clk-mmc-phase.c @@ -0,0 +1,149 @@ +/* + * Copyright 2014 Google, Inc + * Author: Alexandru M Stan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "clk.h" + +struct rockchip_mmc_clock_phase { + struct clk_hw hw; + void __iomem*reg; + int id; + int shift; +}; + +#define to_phase(_hw) container_of(_hw, struct rockchip_mmc_clock_phase, hw) + +static unsigned long rockchip_mmc_recalc(struct clk_hw *hw, +unsigned long parent_rate) +{ + return parent_rate / 2; +} + +#define ROCKCHIP_MMC_DELAY_SEL BIT(10) +#define ROCKCHIP_MMC_DEGREE_MASK 0x3 +#define ROCKCHIP_MMC_DELAYNUM_OFFSET 2 +#define ROCKCHIP_MMC_DELAYNUM_MASK (0xff << ROCKCHIP_MMC_DELAYNUM_OFFSET) + +#define PSECS_PER_SEC 1LL + +/* + * Each fine delay is between 40ps-80ps. Assume each fine delay is 60ps to + * simplify calculations. So 45degs could be anywhere between 33deg and 66deg. + */ +#define DELAY_ELEMENT_PSEC 60 + +static int rockchip_mmc_get_phase(struct clk_hw *hw) +{ + struct rockchip_mmc_clock_phase *phase = to_phase(hw); + unsigned long rate = clk_get_rate(hw->clk); + u32 raw_value; + u16 degrees; + u8 delay_num = 0; + + raw_value = readl(phase->reg) >> (phase->shift); + + degrees = (raw_value & ROCKCHIP_MMC_DEGREE_MASK) * 90; + + if (raw_value & ROCKCHIP_MMC_DELAY_SEL) { + /* degrees/delaynum * 1 */ + unsigned long factor = (DELAY_ELEMENT_PSEC / 10) * 36 * + (rate / 100); + + delay_num = (raw_value & ROCKCHIP_MMC_DELAYNUM_MASK); + delay_num >>= ROCKCHIP_MMC_DELAYNUM_OFFSET; + degrees += delay_num * factor / 1; + } + + return degrees % 360; +} + +static int rockchip_mmc_set_phase(struct clk_hw *hw, int degrees) +{ + struct rockchip_mmc_clock_phase *phase = to_phase(hw); + unsigned long rate = clk_get_rate(hw->clk); + u8 nineties, is_fortyfive; + u8 delay_num = 0; + u32 raw_value; + + degrees -= degrees % 45; + + nineties = degrees / 90; + is_fortyfive = (degrees % 90) == 45; + + if (is_fortyfive) { + u64 delay; + + delay = PSECS_PER_SEC; + do_div(delay, rate); + do_div(delay, 360 / 45); + do_div(delay, DELAY_ELEMENT_PSEC); + + delay_num = (u8) min(delay, 255ULL); + } + + raw_value = ROCKCHIP_MMC_DELAY_SEL; + raw_value |= delay_num << ROCKCHIP_MMC_DELAYNUM_OFFSET; + raw_value |= nineties; + writel(HIWORD_UPDATE(raw_value, 0x, phase->shift), phase->reg); + + pr_debug("%s->set_phase(%d) delay_nums=%u reg[0x%p]=0x%x actual_degrees=%d\n", + __clk_get_name(hw->clk), degrees, delay_num, + phase->reg, raw_value>>phase->shift, + rockchip_mmc_get_phase(hw) + ); + + return 0; +} + +static const struct clk_ops rockchip_phase_clk_ops = { +
[PATCH v2 2/2] clk: rockchip: Add support for the mmc clock phases using the framework
The drive and sample phases are generated by dividing an upstream parent clock by 2, this allows us to adjust the phase by 90 deg. There's also an option to have up to 255 delay elements (40-80 picoseconds long). This driver uses those elements (under the assumption that they're 60ps long) to generate a rough 45deg (which might be from 33deg to 66deg) setting. Suggested-by: Heiko Stuebner he...@sntech.de Signed-off-by: Alexandru M Stan ams...@chromium.org --- Changes in v2: - fixed my cc/to list - removed dangling #DEFINE DEBUG drivers/clk/rockchip/Makefile| 1 + drivers/clk/rockchip/clk-mmc-phase.c | 149 +++ drivers/clk/rockchip/clk-rk3288.c| 12 +++ drivers/clk/rockchip/clk.c | 8 ++ drivers/clk/rockchip/clk.h | 23 ++ 5 files changed, 193 insertions(+) create mode 100644 drivers/clk/rockchip/clk-mmc-phase.c diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index bd8514d..2714097 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -6,6 +6,7 @@ obj-y += clk-rockchip.o obj-y += clk.o obj-y += clk-pll.o obj-y += clk-cpu.o +obj-y += clk-mmc-phase.o obj-$(CONFIG_RESET_CONTROLLER) += softrst.o obj-y += clk-rk3188.o diff --git a/drivers/clk/rockchip/clk-mmc-phase.c b/drivers/clk/rockchip/clk-mmc-phase.c new file mode 100644 index 000..763df4b --- /dev/null +++ b/drivers/clk/rockchip/clk-mmc-phase.c @@ -0,0 +1,149 @@ +/* + * Copyright 2014 Google, Inc + * Author: Alexandru M Stan ams...@chromium.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include linux/slab.h +#include linux/clk-provider.h +#include clk.h + +struct rockchip_mmc_clock_phase { + struct clk_hw hw; + void __iomem*reg; + int id; + int shift; +}; + +#define to_phase(_hw) container_of(_hw, struct rockchip_mmc_clock_phase, hw) + +static unsigned long rockchip_mmc_recalc(struct clk_hw *hw, +unsigned long parent_rate) +{ + return parent_rate / 2; +} + +#define ROCKCHIP_MMC_DELAY_SEL BIT(10) +#define ROCKCHIP_MMC_DEGREE_MASK 0x3 +#define ROCKCHIP_MMC_DELAYNUM_OFFSET 2 +#define ROCKCHIP_MMC_DELAYNUM_MASK (0xff ROCKCHIP_MMC_DELAYNUM_OFFSET) + +#define PSECS_PER_SEC 1LL + +/* + * Each fine delay is between 40ps-80ps. Assume each fine delay is 60ps to + * simplify calculations. So 45degs could be anywhere between 33deg and 66deg. + */ +#define DELAY_ELEMENT_PSEC 60 + +static int rockchip_mmc_get_phase(struct clk_hw *hw) +{ + struct rockchip_mmc_clock_phase *phase = to_phase(hw); + unsigned long rate = clk_get_rate(hw-clk); + u32 raw_value; + u16 degrees; + u8 delay_num = 0; + + raw_value = readl(phase-reg) (phase-shift); + + degrees = (raw_value ROCKCHIP_MMC_DEGREE_MASK) * 90; + + if (raw_value ROCKCHIP_MMC_DELAY_SEL) { + /* degrees/delaynum * 1 */ + unsigned long factor = (DELAY_ELEMENT_PSEC / 10) * 36 * + (rate / 100); + + delay_num = (raw_value ROCKCHIP_MMC_DELAYNUM_MASK); + delay_num = ROCKCHIP_MMC_DELAYNUM_OFFSET; + degrees += delay_num * factor / 1; + } + + return degrees % 360; +} + +static int rockchip_mmc_set_phase(struct clk_hw *hw, int degrees) +{ + struct rockchip_mmc_clock_phase *phase = to_phase(hw); + unsigned long rate = clk_get_rate(hw-clk); + u8 nineties, is_fortyfive; + u8 delay_num = 0; + u32 raw_value; + + degrees -= degrees % 45; + + nineties = degrees / 90; + is_fortyfive = (degrees % 90) == 45; + + if (is_fortyfive) { + u64 delay; + + delay = PSECS_PER_SEC; + do_div(delay, rate); + do_div(delay, 360 / 45); + do_div(delay, DELAY_ELEMENT_PSEC); + + delay_num = (u8) min(delay, 255ULL); + } + + raw_value = ROCKCHIP_MMC_DELAY_SEL; + raw_value |= delay_num ROCKCHIP_MMC_DELAYNUM_OFFSET; + raw_value |= nineties; + writel(HIWORD_UPDATE(raw_value, 0x, phase-shift), phase-reg); + + pr_debug(%s-set_phase(%d) delay_nums=%u reg[0x%p]=0x%x actual_degrees=%d\n, + __clk_get_name(hw-clk), degrees, delay_num, + phase-reg, raw_valuephase-shift, + rockchip_mmc_get_phase(hw) + ); + + return 0; +} + +static