// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * PWM controller driver for Amlogic Meson SoCs. * * This PWM is only a set of Gates, Dividers and Counters: * PWM output is achieved by calculating a clock that permits calculating * two periods (low and high). The counter then has to be set to switch after * N cycles for the first half period. * The hardware has no "polarity" setting. This driver reverses the period * cycles (the low length is inverted with the high length) for * PWM_POLARITY_INVERSED. This means that .get_state cannot read the polarity * from the hardware. * Setting the duty cycle will disable and re-enable the PWM output. * Disabling the PWM stops the output immediately (without waiting for the * current period to complete first). * * The public S912 (GXM) datasheet contains some documentation for this PWM * controller starting on page 543: * https://dl.khadas.com/Hardware/VIM2/Datasheet/S912_Datasheet_V0.220170314publicversion-Wesion.pdf * An updated version of this IP block is found in S922X (G12B) SoCs. The * datasheet contains the description for this IP block revision starting at * page 1084: * https://dn.odroid.com/S922X/ODROID-N2/Datasheet/S922X_Public_Datasheet_V0.2.pdf * * Copyright (c) 2016 BayLibre, SAS. * Author: Neil Armstrong * Copyright (C) 2014 Amlogic, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_AMLOGIC_MODIFY #include #define FOR_BACK_TRACE //#define M_DEBUG #ifdef M_DEBUG #define DEBUG #define PWM_DBG(fmt, ...) pr_info("%s ," fmt, "[MESON PWM]", ##__VA_ARGS__) #else #define PWM_DBG(fmt, ...) #endif /*endif M_DEBUG*/ #else #define REG_PWM_A 0x0 #define REG_PWM_B 0x4 #define PWM_LOW_MASK GENMASK(15, 0) #define PWM_HIGH_MASK GENMASK(31, 16) #define REG_MISC_AB 0x8 #define MISC_B_CLK_EN BIT(23) #define MISC_A_CLK_EN BIT(15) #define MISC_CLK_DIV_MASK 0x7f #define MISC_B_CLK_DIV_SHIFT 16 #define MISC_A_CLK_DIV_SHIFT 8 #define MISC_B_CLK_SEL_SHIFT 6 #define MISC_A_CLK_SEL_SHIFT 4 #define MISC_CLK_SEL_MASK 0x3 #define MISC_B_EN BIT(1) #define MISC_A_EN BIT(0) #define MESON_NUM_PWMS 2 struct meson_pwm_channel { unsigned int hi; unsigned int lo; u8 pre_div; struct clk *clk_parent; struct clk_mux mux; struct clk *clk; }; struct meson_pwm_data { const char * const *parent_names; unsigned int num_parents; }; struct meson_pwm { struct pwm_chip chip; const struct meson_pwm_data *data; struct meson_pwm_channel channels[MESON_NUM_PWMS]; void __iomem *base; /* * Protects register (write) access to the REG_MISC_AB register * that is shared between the two PWMs. */ spinlock_t lock; }; #endif /*endif CONFIG_AMLOGIC_MODIFY*/ #ifdef CONFIG_AMLOGIC_MODIFY struct meson_channel_ext_clk_data { u8 ext_clk_div_shift; u32 ext_clk_en_mask; }; #define MESON_NEW_EVEN_EXT_CLK_DATA 0 #define MESON_NEW_ODD_EXT_CLK_DATA 1 /*need add external clk*/ static struct meson_channel_ext_clk_data channel_ext_clk_data[] = { { .ext_clk_div_shift = EXT_CLK_A_DIV_SHIFT, .ext_clk_en_mask = EXT_CLK_A_EN, }, { .ext_clk_div_shift = EXT_CLK_B_DIV_SHIFT, .ext_clk_en_mask = EXT_CLK_B_EN, } }; struct meson_pwm_channel_data { u8 reg_offset; u8 clk_sel_shift; u8 clk_div_shift; u32 clk_en_mask; u32 pwm_en_mask; }; static struct meson_pwm_channel_data meson_pwm_per_channel_data[MESON_DOUBLE_NUM_PWMS] = {/*need add external clk*/ { .reg_offset = REG_PWM_A, .clk_sel_shift = MISC_A_CLK_SEL_SHIFT, .clk_div_shift = MISC_A_CLK_DIV_SHIFT, .clk_en_mask = MISC_A_CLK_EN, .pwm_en_mask = MISC_A_EN, }, { .reg_offset = REG_PWM_B, .clk_sel_shift = MISC_B_CLK_SEL_SHIFT, .clk_div_shift = MISC_B_CLK_DIV_SHIFT, .clk_en_mask = MISC_B_CLK_EN, .pwm_en_mask = MISC_B_EN, }, { .reg_offset = REG_PWM_A2, .clk_sel_shift = MISC_A_CLK_SEL_SHIFT, .clk_div_shift = MISC_A_CLK_DIV_SHIFT, .clk_en_mask = MISC_A_CLK_EN, .pwm_en_mask = MISC_A2_EN, }, { .reg_offset = REG_PWM_B2, .clk_sel_shift = MISC_B_CLK_SEL_SHIFT, .clk_div_shift = MISC_B_CLK_DIV_SHIFT, .clk_en_mask = MISC_B_CLK_EN, .pwm_en_mask = MISC_B2_EN, }, }; #else static struct meson_pwm_channel_data { u8 reg_offset; u8 clk_sel_shift; u8 clk_div_shift; u32 clk_en_mask; u32 pwm_en_mask; } meson_pwm_per_channel_data[] = { { .reg_offset = REG_PWM_A, .clk_sel_shift = MISC_A_CLK_SEL_SHIFT, .clk_div_shift = MISC_A_CLK_DIV_SHIFT, .clk_en_mask = MISC_A_CLK_EN, .pwm_en_mask = MISC_A_EN, }, { .reg_offset = REG_PWM_B, .clk_sel_shift = MISC_B_CLK_SEL_SHIFT, .clk_div_shift = MISC_B_CLK_DIV_SHIFT, .clk_en_mask = MISC_B_CLK_EN, .pwm_en_mask = MISC_B_EN, }, }; #endif //CONFIG_AMLOGIC_MODIFY #define NORMAL_RW_NODE 0 #ifdef CONFIG_AMLOGIC_MODIFY struct meson_pwm *to_meson_pwm(struct pwm_chip *chip) #else static inline struct meson_pwm *to_meson_pwm(struct pwm_chip *chip) #endif { return pwmchip_get_drvdata(chip); } EXPORT_SYMBOL(to_meson_pwm); static int meson_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { struct meson_pwm *meson = to_meson_pwm(chip); struct meson_pwm_channel *channel; struct device *dev = &chip->dev; int err; channel = &meson->channels[pwm->hwpwm]; #ifdef CONFIG_AMLOGIC_MODIFY if (meson->data->channel_separated && (pwm->hwpwm != MESON_PWM_0 && pwm->hwpwm != MESON_PWM_2)) { dev_err(dev, "Only MESON_PWM_0 and MESON_PWM_2 could be used on separated pwm chip\n"); return -EINVAL; } #endif #ifdef CONFIG_AMLOGIC_MODIFY if (!meson->data->extern_clk) #endif if (channel->clk_parent) { err = clk_set_parent(channel->clk, channel->clk_parent); if (err < 0) { dev_err(dev, "failed to set parent %s for %s: %d\n", __clk_get_name(channel->clk_parent), __clk_get_name(channel->clk), err); return err; } } #ifdef CONFIG_AMLOGIC_MODIFY if (meson->data->extern_clk) { return 0; } #endif err = clk_prepare_enable(channel->clk); if (err < 0) { dev_err(dev, "failed to enable clock %s: %d\n", __clk_get_name(channel->clk), err); return err; } #ifdef CONFIG_AMLOGIC_MODIFY channel->clk_rate = clk_get_rate(channel->clk); #endif return 0; } static void meson_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) { struct meson_pwm *meson = to_meson_pwm(chip); struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm]; if (meson->data->extern_clk) return; if (channel) clk_disable_unprepare(channel->clk); } static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm, const struct pwm_state *state) { struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm]; unsigned int duty, period, pre_div, cnt, duty_cnt; unsigned long fin_freq = -1; duty = state->duty_cycle; period = state->period; if (state->polarity == PWM_POLARITY_INVERSED) duty = period - duty; #ifdef CONFIG_AMLOGIC_MODIFY fin_freq = channel->clk_rate; #else /*clk_get_rate()not use in Interrupt context*/ fin_freq = clk_get_rate(channel->clk); #endif if (fin_freq == 0) { dev_err(&meson->chip->dev, "invalid source clock frequency\n"); return -EINVAL; } dev_dbg(&meson->chip->dev, "fin_freq: %lu Hz\n", fin_freq); pre_div = DIV64_U64_ROUND_CLOSEST((u64)fin_freq * (u64)period, (u64)NSEC_PER_SEC * 0xffffLL); if (pre_div > MISC_CLK_DIV_MASK) { dev_err(&meson->chip->dev, "unable to get period pre_div\n"); return -EINVAL; } cnt = DIV64_U64_ROUND_CLOSEST((u64)fin_freq * (u64)period, (u64)NSEC_PER_SEC * (u64)(pre_div + 1)); if (cnt > 0xffff) { dev_err(&meson->chip->dev, "unable to get period cnt\n"); return -EINVAL; } dev_dbg(&meson->chip->dev, "period=%u pre_div=%u cnt=%u\n", period, pre_div, cnt); if (duty == period) { channel->pre_div = pre_div; channel->hi = cnt; channel->lo = 0; } else if (duty == 0) { channel->pre_div = pre_div; channel->hi = 0; channel->lo = cnt; } else { /* Then check is we can have the duty with the same pre_div */ duty_cnt = DIV64_U64_ROUND_CLOSEST(fin_freq * (u64)duty, NSEC_PER_SEC * (pre_div + 1)); if (duty_cnt > 0xffff) { dev_err(&meson->chip->dev, "unable to get duty cycle\n"); return -EINVAL; } dev_dbg(&meson->chip->dev, "duty=%u pre_div=%u duty_cnt=%u\n", duty, pre_div, duty_cnt); channel->pre_div = pre_div; #ifndef CONFIG_AMLOGIC_MODIFY channel->hi = duty_cnt; channel->lo = cnt - duty_cnt; #else if (duty_cnt == 0) duty_cnt++; channel->hi = duty_cnt - 1; channel->lo = cnt - duty_cnt - 1; #endif } #ifdef CONFIG_AMLOGIC_MODIFY /* * duty_cycle equal 0% and 100%,constant should be enabled, * high and low count will not incease one; * otherwise, high and low count increase one. */ if (duty == period || duty == 0) pwm_constant_enable(meson, pwm->hwpwm); else pwm_constant_disable(meson, pwm->hwpwm); #endif return 0; } static void meson_pwm_enable(struct meson_pwm *meson, struct pwm_device *pwm) { struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm]; struct meson_pwm_channel_data *channel_data; struct meson_channel_ext_clk_data *ext_clk_data; unsigned long flags; u32 value; channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; spin_lock_irqsave(&meson->lock, flags); /*following filed are invalid on new soc*/ value = readl(meson->base + REG_MISC_AB); value &= ~(MISC_CLK_DIV_MASK << channel_data->clk_div_shift); value |= channel->pre_div << channel_data->clk_div_shift; value |= channel_data->clk_en_mask; writel(value, meson->base + REG_MISC_AB); /*upper filed are invalid on new soc*/ value = FIELD_PREP(PWM_HIGH_MASK, channel->hi) | FIELD_PREP(PWM_LOW_MASK, channel->lo); writel(value, meson->base + channel_data->reg_offset); value = readl(meson->base + REG_MISC_AB); value |= channel_data->pwm_en_mask; writel(value, meson->base + REG_MISC_AB); #ifdef CONFIG_AMLOGIC_MODIFY PWM_DBG("%s,set pwm hi 0x%x, lo 0x%x\n", __func__, channel->hi, channel->lo); if (meson->data->extern_clk) { if (meson->data->channel_separated) { if (meson->data->even_channel) ext_clk_data = &channel_ext_clk_data[MESON_NEW_EVEN_EXT_CLK_DATA]; else ext_clk_data = &channel_ext_clk_data[MESON_NEW_ODD_EXT_CLK_DATA]; } else { ext_clk_data = &channel_ext_clk_data[pwm->hwpwm % MESON_NUM_PWMS]; } value = readl(meson->ext_clk_base); value &= ~(EXT_CLK_DIV_MASK << ext_clk_data->ext_clk_div_shift); value |= channel->pre_div << ext_clk_data->ext_clk_div_shift; value |= ext_clk_data->ext_clk_en_mask; writel(value, meson->ext_clk_base); channel->clk_div = channel->pre_div; } #endif spin_unlock_irqrestore(&meson->lock, flags); } static void meson_pwm_disable(struct meson_pwm *meson, struct pwm_device *pwm) { unsigned long flags; u32 value; spin_lock_irqsave(&meson->lock, flags); value = readl(meson->base + REG_MISC_AB); value &= ~meson_pwm_per_channel_data[pwm->hwpwm].pwm_en_mask; writel(value, meson->base + REG_MISC_AB); spin_unlock_irqrestore(&meson->lock, flags); } static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { struct meson_pwm *meson = to_meson_pwm(chip); struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm]; int err = 0; if (!state) return -EINVAL; if (!state->enabled) { if (state->polarity == PWM_POLARITY_INVERSED) { /* * This IP block revision doesn't have an "always high" * setting which we can use for "inverted disabled". * Instead we achieve this using the same settings * that we use a pre_div of 0 (to get the shortest * possible duration for one "count") and * "period == duty_cycle". This results in a signal * which is LOW for one "count", while being HIGH for * the rest of the (so the signal is HIGH for slightly * less than 100% of the period, but this is the best * we can achieve). */ channel->pre_div = 0; channel->hi = ~0; channel->lo = 0; meson_pwm_enable(meson, pwm); } else { meson_pwm_disable(meson, pwm); } } else { err = meson_pwm_calc(meson, pwm, state); if (err < 0) return err; #ifdef CONFIG_AMLOGIC_MODIFY PWM_DBG("%s, calc pwm state period: %llu ns, duty: %llu ns\n", __func__, state->period, state->duty_cycle); #endif meson_pwm_enable(meson, pwm); } return 0; } static unsigned int meson_pwm_cnt_to_ns(struct pwm_chip *chip, struct pwm_device *pwm, u32 cnt) { struct meson_pwm *meson = to_meson_pwm(chip); struct meson_pwm_channel *channel; unsigned long fin_freq; u32 fin_ns; #ifdef CONFIG_AMLOGIC_MODIFY unsigned long div_fin_freq; /* to_meson_pwm() can only be used after .get_state() is called */ channel = &meson->channels[pwm->hwpwm]; if (meson->data->extern_clk) fin_freq = channel->clk_rate; else fin_freq = clk_get_rate(channel->clk); div_fin_freq = DIV_ROUND_CLOSEST_ULL(fin_freq, channel->pre_div + 1); fin_ns = DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC, div_fin_freq); PWM_DBG("%s,the final clk rate:%uns pr_div:%d", __func__, fin_ns, channel->pre_div + 1); return cnt * fin_ns; #else /* to_meson_pwm() can only be used after .get_state() is called */ channel = &meson->channels[pwm->hwpwm]; fin_freq = clk_get_rate(channel->clk); if (fin_freq == 0) return 0; fin_ns = DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC, fin_freq); return cnt * fin_ns * (channel->pre_div + 1); #endif } #ifdef CONFIG_AMLOGIC_MODIFY static void meson_v2_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { struct meson_pwm *meson = to_meson_pwm(chip); struct meson_pwm_channel *channel; struct meson_pwm_channel_data *channel_data; u32 value, tmp_value, en_mask, constant_mask; bool constant_enabled; struct meson_channel_ext_clk_data *ext_clk_data; channel = &meson->channels[pwm->hwpwm]; channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; if (!state) return; switch (pwm->hwpwm) { case 0: en_mask = MISC_A_EN; constant_mask = MISC_A_CONSTANT; break; case 1: if (meson->data->channel_separated) return;// after s7, only channel 0 and 2 used. en_mask = MISC_B_EN; constant_mask = MISC_B_CONSTANT; break; case 2: en_mask = MISC_A2_EN; constant_mask = MISC_A_CONSTANT; break; case 3: if (meson->data->channel_separated) return;// after s7, only channel 0 and 2 used. en_mask = MISC_B2_EN; constant_mask = MISC_B_CONSTANT; break; default: return; } value = readl(meson->base + REG_MISC_AB); tmp_value = value; state->enabled = (value & en_mask) != 0; constant_enabled = (value & constant_mask) != 0; #ifdef FOR_BACK_TRACE value = readl(meson->base + channel_data->reg_offset); channel->lo = FIELD_GET(PWM_LOW_MASK, value); channel->hi = FIELD_GET(PWM_HIGH_MASK, value); PWM_DBG("%s, get pwm state hi 0x%x, li 0x%x\n", __func__, channel->hi, channel->lo); if (meson->data->extern_clk) { if (meson->data->channel_separated) { if (meson->data->even_channel) ext_clk_data = &channel_ext_clk_data[MESON_NEW_EVEN_EXT_CLK_DATA]; else ext_clk_data = &channel_ext_clk_data[MESON_NEW_ODD_EXT_CLK_DATA]; } else { ext_clk_data = &channel_ext_clk_data[pwm->hwpwm % MESON_NUM_PWMS]; } value = readl(meson->ext_clk_base); tmp_value = value >> ext_clk_data->ext_clk_div_shift; channel->pre_div = FIELD_GET(EXT_CLK_DIV_MASK, tmp_value); } if (channel->lo == 0) { if (constant_enabled) { state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->hi); state->duty_cycle = state->period; } else { state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->hi + 2); state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, channel->hi + 1); } } else if (channel->hi == 0) { if (constant_enabled) { state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->lo); state->duty_cycle = 0; } else { state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->lo + 2); state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, channel->hi + 1); } } else { state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->lo + channel->hi + 2); state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, channel->hi + 1); } PWM_DBG("%s, get pwm state period: %llu ns, duty: %llu ns\n", __func__, state->period, state->duty_cycle); #endif /*FOR_BACK_TRACE*/ } #endif static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { struct meson_pwm *meson = to_meson_pwm(chip); struct meson_pwm_channel_data *channel_data; struct meson_pwm_channel *channel; u32 value, tmp; #ifdef CONFIG_AMLOGIC_MODIFY bool constant_enabled; u32 constant_mask; #endif if (!state) goto state_return; #ifdef CONFIG_AMLOGIC_MODIFY if (meson->data->extern_clk) { meson_v2_pwm_get_state(chip, pwm, state); goto state_return; } switch (pwm->hwpwm) { case 0: constant_mask = MISC_A_CONSTANT; break; case 1: constant_mask = MISC_B_CONSTANT; break; case 2: constant_mask = MISC_A_CONSTANT; break; case 3: constant_mask = MISC_B_CONSTANT; goto state_return; default: goto state_return; } #endif channel = &meson->channels[pwm->hwpwm]; channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; value = readl(meson->base + REG_MISC_AB); tmp = channel_data->pwm_en_mask | channel_data->clk_en_mask; state->enabled = (value & tmp) == tmp; #ifdef CONFIG_AMLOGIC_MODIFY constant_enabled = (value & constant_mask) != 0; #endif tmp = value >> channel_data->clk_div_shift; channel->pre_div = FIELD_GET(MISC_CLK_DIV_MASK, tmp); value = readl(meson->base + channel_data->reg_offset); channel->lo = FIELD_GET(PWM_LOW_MASK, value); channel->hi = FIELD_GET(PWM_HIGH_MASK, value); #if defined(CONFIG_AMLOGIC_MODIFY) && defined(FOR_BACK_TRACE) PWM_DBG("%s, get pwm state hi 0x%x, li 0x%x\n", __func__, channel->hi, channel->lo); if (channel->lo == 0) { if (constant_enabled) { state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->hi); state->duty_cycle = state->period; } else { state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->hi + 2); state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, channel->hi + 1); } } else if (channel->hi == 0) { if (constant_enabled) { state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->lo); state->duty_cycle = 0; } else { state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->lo + 2); state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, channel->hi + 1); } } else { state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->lo + channel->hi + 2); state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, channel->hi + 1); } PWM_DBG("%s, get pwm state period: %lluns, duty: %lluns\n", __func__, state->period, state->duty_cycle); #else if (channel->lo == 0) { state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->hi); state->duty_cycle = state->period; } else if (channel->lo >= channel->hi) { state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->lo + channel->hi); state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, channel->hi); } else { state->period = 0; state->duty_cycle = 0; } #endif state_return: return 0; } static const struct pwm_ops meson_pwm_ops = { .request = meson_pwm_request, .free = meson_pwm_free, .apply = meson_pwm_apply, .get_state = meson_pwm_get_state, }; #ifndef CONFIG_AMLOGIC_REMOVE_OLD static char *pwm_meson8b_parent_names[] __refdata = { "xtal", "vid_pll", "fclk_div4", "fclk_div3" }; static struct meson_pwm_data pwm_meson8b_data __refdata = { .parent_names = pwm_meson8b_parent_names, .num_parents = ARRAY_SIZE(pwm_meson8b_parent_names), #ifdef CONFIG_AMLOGIC_MODIFY .double_channel = false, #endif }; static char *pwm_gxbb_parent_names[] __refdata = { "xtal", "hdmi_pll", "fclk_div4", "fclk_div3" }; static struct meson_pwm_data pwm_gxbb_data __refdata = { .parent_names = pwm_gxbb_parent_names, .num_parents = ARRAY_SIZE(pwm_gxbb_parent_names), #ifdef CONFIG_AMLOGIC_MODIFY .double_channel = false, #endif }; /* * Only the 2 first inputs of the GXBB AO PWMs are valid * The last 2 are grounded */ static char *pwm_gxbb_ao_parent_names[] __refdata = { "xtal", "clk81" }; static struct meson_pwm_data pwm_gxbb_ao_data __refdata = { .parent_names = pwm_gxbb_ao_parent_names, .num_parents = ARRAY_SIZE(pwm_gxbb_ao_parent_names), #ifdef CONFIG_AMLOGIC_MODIFY .double_channel = false, #endif }; static char *pwm_axg_ee_parent_names[] __refdata = { "xtal", "fclk_div5", "fclk_div4", "fclk_div3" }; static struct meson_pwm_data pwm_axg_ee_data __refdata = { .parent_names = pwm_axg_ee_parent_names, .num_parents = ARRAY_SIZE(pwm_axg_ee_parent_names), #ifdef CONFIG_AMLOGIC_MODIFY .double_channel = true, #endif }; static char *pwm_axg_ao_parent_names[] __refdata = { "xtal", "aoclk81", "fclk_div4", "fclk_div5" }; static struct meson_pwm_data pwm_axg_ao_data __refdata = { .parent_names = pwm_axg_ao_parent_names, .num_parents = ARRAY_SIZE(pwm_axg_ao_parent_names), #ifdef CONFIG_AMLOGIC_MODIFY .double_channel = true, #endif }; #endif #ifndef CONFIG_AMLOGIC_ZAPPER_CUT static char *pwm_g12a_ao_ab_parent_names[] __refdata = { "xtal", "aoclk81", "fclk_div4", "fclk_div5" }; static struct meson_pwm_data pwm_g12a_ao_ab_data __refdata = { .parent_names = pwm_g12a_ao_ab_parent_names, .num_parents = ARRAY_SIZE(pwm_g12a_ao_ab_parent_names), #ifdef CONFIG_AMLOGIC_MODIFY .double_channel = true, #endif }; static char *pwm_g12a_ao_cd_parent_names[] __refdata = { "xtal", "aoclk81", }; static struct meson_pwm_data pwm_g12a_ao_cd_data __refdata = { .parent_names = pwm_g12a_ao_cd_parent_names, .num_parents = ARRAY_SIZE(pwm_g12a_ao_cd_parent_names), #ifdef CONFIG_AMLOGIC_MODIFY .double_channel = true, #endif }; static char *pwm_g12a_ee_parent_names[] __refdata = { "xtal", "hdmi_pll", "fclk_div4", "fclk_div3" }; static struct meson_pwm_data pwm_g12a_ee_data __refdata = { .parent_names = pwm_g12a_ee_parent_names, .num_parents = ARRAY_SIZE(pwm_g12a_ee_parent_names), #ifdef CONFIG_AMLOGIC_MODIFY .double_channel = true, #endif }; #endif //end of CONFIG_AMLOGIC_ZAPPER_CUT #ifdef CONFIG_AMLOGIC_MODIFY #ifndef CONFIG_AMLOGIC_ZAPPER_CUT static char *pwm_t5d_parent_names[] __refdata = { "xtal", "clk81", "fclk_div4", "fclk_div5" }; static struct meson_pwm_data pwm_t5d_data __refdata = { .parent_names = pwm_t5d_parent_names, .num_parents = ARRAY_SIZE(pwm_t5d_parent_names), .double_channel = true, }; #endif //end of CONFIG_AMLOGIC_ZAPPER_CUT static struct meson_pwm_data pwm_v2_data __refdata = { .double_channel = true, .extern_clk = true, }; static struct meson_pwm_data pwm_odd_data __refdata = { .double_channel = true, .extern_clk = true, .channel_separated = true, }; static struct meson_pwm_data pwm_even_data __refdata = { .double_channel = true, .extern_clk = true, .channel_separated = true, .even_channel = true, }; #endif static const struct of_device_id meson_pwm_matches[] = { #ifndef CONFIG_AMLOGIC_REMOVE_OLD { .compatible = "amlogic,meson8b-pwm", .data = &pwm_meson8b_data }, { .compatible = "amlogic,meson-gxbb-pwm", .data = &pwm_gxbb_data }, { .compatible = "amlogic,meson-gxbb-ao-pwm", .data = &pwm_gxbb_ao_data }, { .compatible = "amlogic,meson-axg-ee-pwm", .data = &pwm_axg_ee_data }, { .compatible = "amlogic,meson-axg-ao-pwm", .data = &pwm_axg_ao_data }, #endif #ifndef CONFIG_AMLOGIC_ZAPPER_CUT { .compatible = "amlogic,meson-g12a-ee-pwm", .data = &pwm_g12a_ee_data }, { .compatible = "amlogic,meson-g12a-ao-pwm-ab", .data = &pwm_g12a_ao_ab_data }, { .compatible = "amlogic,meson-g12a-ao-pwm-cd", .data = &pwm_g12a_ao_cd_data }, #endif #ifdef CONFIG_AMLOGIC_MODIFY #ifndef CONFIG_AMLOGIC_ZAPPER_CUT { .compatible = "amlogic,meson-tm2-ee-pwm", .data = &pwm_g12a_ee_data }, { .compatible = "amlogic,meson-tm2-ao-pwm-ab", .data = &pwm_g12a_ao_ab_data }, { .compatible = "amlogic,meson-tm2-ao-pwm-cd", .data = &pwm_g12a_ao_cd_data }, { .compatible = "amlogic,meson-t5d-ee-pwm", .data = &pwm_t5d_data }, #endif //end of CONFIG_AMLOGIC_ZAPPER_CUT { .compatible = "amlogic,meson-v2-pwm", .data = &pwm_v2_data }, { .compatible = "amlogic,meson-odd-pwm", .data = &pwm_odd_data }, { .compatible = "amlogic,meson-even-pwm", .data = &pwm_even_data }, #endif {}, }; MODULE_DEVICE_TABLE(of, meson_pwm_matches); static int meson_pwm_init_channels(struct meson_pwm *meson) { struct device *dev = pwmchip_parent(meson->chip); struct clk_init_data init; unsigned int i; char name[255]; int err; for (i = 0; i < meson->chip->npwm; i++) { struct meson_pwm_channel *channel = &meson->channels[i]; snprintf(name, sizeof(name), "%s#mux%u", dev_name(dev), i); init.name = name; init.ops = &clk_mux_ops; init.flags = 0; init.parent_names = (const char *const *)meson->data->parent_names; init.num_parents = meson->data->num_parents; channel->mux.reg = meson->base + REG_MISC_AB; channel->mux.shift = meson_pwm_per_channel_data[i].clk_sel_shift; channel->mux.mask = MISC_CLK_SEL_MASK; channel->mux.flags = 0; channel->mux.lock = &meson->lock; channel->mux.table = NULL; channel->mux.hw.init = &init; channel->clk = devm_clk_register(dev, &channel->mux.hw); if (IS_ERR(channel->clk)) { err = PTR_ERR(channel->clk); dev_err(dev, "failed to register %s: %d\n", name, err); return err; } snprintf(name, sizeof(name), "clkin%u", i); channel->clk_parent = devm_clk_get_optional(dev, name); if (IS_ERR(channel->clk_parent)) return PTR_ERR(channel->clk_parent); } return 0; } #ifdef CONFIG_AMLOGIC_MODIFY static int meson_pwm_v2_init_channels(struct meson_pwm *meson) { struct meson_pwm_channel *channels = meson->channels; struct device *dev = pwmchip_parent(meson->chip); struct device_node *np = dev->of_node; unsigned int i; for (i = 0; i < (meson->chip->npwm / 2); i++) { (channels + i)->clk = of_clk_get(np, i); if (IS_ERR((channels + i)->clk)) { dev_err(&meson->chip->dev, "can't get device clock\n"); return PTR_ERR((channels + i)->clk); } (channels + i)->clk_rate = clk_get_rate((channels + i)->clk); PWM_DBG("get clock sel%d freq= %u\n", i, (channels + i)->clk_rate); (channels + i + 2)->clk = (channels + i)->clk; (channels + i + 2)->clk_rate = (channels + i)->clk_rate; if (meson->data->channel_separated) break;// after s7, only channel 0 and 2 used. } return 0; } static struct regmap_config meson_pwm_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, }; #endif #ifdef CONFIG_HIBERNATION static int meson_pwm_freeze(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct meson_pwm *meson = platform_get_drvdata(pdev); int i; pinctrl_pm_select_sleep_state(dev); for (i = 0; i < PWM_REG_NUMS; i++) { if (i == PWM_REG_NUMS - 1 && meson->data->extern_clk)//read clock reg meson->regs_restore[i] = readl(meson->ext_clk_base); else meson->regs_restore[i] = readl(meson->base + 4 * i); pr_debug("pwm freeze, reg%d: 0x%x\n", i, meson->regs_restore[i]); } return 0; } static int meson_pwm_thaw(struct device *dev) { return 0; } static int meson_pwm_restore(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct meson_pwm *meson = platform_get_drvdata(pdev); int i; pinctrl_pm_select_default_state(dev); for (i = 0; i < PWM_REG_NUMS; i++) { if (i == PWM_REG_NUMS - 1 && meson->data->extern_clk)// restore clock reg writel(meson->regs_restore[i], meson->ext_clk_base); else writel(meson->regs_restore[i], meson->base + 4 * i); pr_debug("pwm restore, reg%d: 0x%x\n", i, meson->regs_restore[i]); } return 0; } static int meson_pwm_pm_suspend(struct device *dev) { return 0; } static int meson_pwm_pm_resume(struct device *dev) { return 0; } const struct dev_pm_ops meson_pwm_pm = { .freeze = meson_pwm_freeze, .thaw = meson_pwm_thaw, .restore = meson_pwm_restore, .suspend = meson_pwm_pm_suspend, .resume = meson_pwm_pm_resume, }; #endif static int meson_pwm_probe(struct platform_device *pdev) { struct meson_pwm *meson; struct resource *regs; struct pwm_chip *chip; int err; int i; #ifdef CONFIG_AMLOGIC_MODIFY struct resource *ext_clk_regs; #endif struct meson_pwm_data *match = (struct meson_pwm_data *)of_device_get_match_data(&pdev->dev); if (match->double_channel) { chip = devm_pwmchip_alloc(&pdev->dev, MESON_DOUBLE_NUM_PWMS, sizeof(*meson)); // chip->npwm = MESON_DOUBLE_NUM_PWMS; } else { chip = devm_pwmchip_alloc(&pdev->dev, MESON_NUM_PWMS, sizeof(*meson)); // chip->npwm = MESON_DOUBLE_NUM_PWMS; } if (IS_ERR(chip)) return PTR_ERR(chip); meson = to_meson_pwm(chip); meson->chip = chip; meson->data = devm_kzalloc(&pdev->dev, sizeof(*meson->data), GFP_KERNEL); if (!meson->data) return -ENOMEM; regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); meson->base = devm_ioremap_resource(&pdev->dev, regs); if (IS_ERR(meson->base)) return PTR_ERR(meson->base); #ifdef CONFIG_AMLOGIC_MODIFY meson_pwm_regmap_config.max_register = resource_size(regs) - 4; meson_pwm_regmap_config.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s", "pwm"); meson->regmap_base = devm_regmap_init_mmio(&pdev->dev, meson->base, &meson_pwm_regmap_config); #endif spin_lock_init(&meson->lock); meson->chip->ops = &meson_pwm_ops; meson->chip->atomic = true; meson->data->num_parents = match->num_parents; meson->data->double_channel = match->double_channel; #ifdef CONFIG_AMLOGIC_MODIFY meson->data->channel_separated = match->channel_separated; meson->data->even_channel = match->even_channel; #endif meson->data->extern_clk = match->extern_clk; meson->data->parent_names = devm_kzalloc(&pdev->dev, sizeof(char *) * (match->num_parents), GFP_KERNEL); if (!meson->data) return -ENOMEM; #ifdef CONFIG_AMLOGIC_MODIFY if (meson->data->extern_clk) { ext_clk_regs = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!ext_clk_regs) { dev_err(&pdev->dev, "can't get reg resource\n"); return -ENODEV; } PWM_DBG("get clk reg start:0x%p end:0x%p\n", (void *)(ext_clk_regs->start), (void *)(ext_clk_regs->end)); meson->ext_clk_base = devm_ioremap(&pdev->dev, ext_clk_regs->start, 4); if (IS_ERR(meson->ext_clk_base)) return PTR_ERR(meson->ext_clk_base); } #endif for (i = 0; i < match->num_parents; i++) meson->data->parent_names[i] = devm_kstrdup(&pdev->dev, match->parent_names[i], GFP_KERNEL); #ifdef CONFIG_AMLOGIC_MODIFY if (meson->data->extern_clk) err = meson_pwm_v2_init_channels(meson); else #endif err = meson_pwm_init_channels(meson); if (err < 0) return err; err = pwmchip_add(meson->chip); if (err < 0) { dev_err(&pdev->dev, "failed to register PWM chip: %d\n", err); return err; } platform_set_drvdata(pdev, meson); #ifdef CONFIG_AMLOGIC_MODIFY /*for constant,blinks functions*/ if (meson->data->double_channel) meson_pwm_sysfs_init(&pdev->dev, NORMAL_RW_NODE); #endif return 0; } static void meson_pwm_remove(struct platform_device *pdev) { struct meson_pwm *meson = platform_get_drvdata(pdev); #ifdef CONFIG_AMLOGIC_MODIFY if (meson->data->double_channel) meson_pwm_sysfs_exit(&pdev->dev); #endif pwmchip_remove(meson->chip); } static struct platform_driver meson_pwm_driver = { .driver = { .name = "meson-pwm", .of_match_table = meson_pwm_matches, #ifdef CONFIG_HIBERNATION .pm = &meson_pwm_pm, #endif }, .probe = meson_pwm_probe, .remove = meson_pwm_remove, }; #ifdef CONFIG_AMLOGIC_MODIFY int __init meson_pwm_init(void) { const struct of_device_id *match_id; int ret; match_id = meson_pwm_matches; meson_pwm_driver.driver.of_match_table = match_id; ret = platform_driver_register(&meson_pwm_driver); return ret; } void __exit meson_pwm_exit(void) { platform_driver_unregister(&meson_pwm_driver); } #else module_platform_driver(meson_pwm_driver); module_exit(meson_pwm_exit); #endif MODULE_DESCRIPTION("Amlogic Meson PWM Generator driver"); MODULE_AUTHOR("Neil Armstrong "); MODULE_LICENSE("Dual BSD/GPL");