diff --git a/MAINTAINERS b/MAINTAINERS index 0e2dd703cd3c..e8aa84b9d187 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13503,3 +13503,10 @@ AMLOGIC CPU hotplug for dvfs and IPA M: Jianxin Pan F: drivers/amlogic/cpu_hotplug/* +AMLOGIC PWM DRIVER +M: Jian Hu +F: drivers/amlogic/pwm/* +F: include/linux/amlogic/pwm_meson.h +F: include/dt-bindings/pwm/meosn.h +F: arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts +F: arch/arm64/boot/dts/amlogic/gxm_q200_2g.dts diff --git a/arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts b/arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts index f11e71b7ca68..fe1cbcea8474 100644 --- a/arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts +++ b/arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts @@ -17,7 +17,6 @@ /dts-v1/; -#include #include "mesongxl.dtsi" #include #include @@ -184,6 +183,36 @@ "usb1", "usb1_to_ddr"; }; + + pwm { + compatible = "amlogic, meson-pwm"; + status = "okay"; + pwm-outputs = ,,,, + ,,,; + pwm-outputs-new = ,,,, + ,,,, + ,,,, + ,,,; + reg = <0x0 0xc1108550 0x0 0x30>, + <0x0 0xc8100550 0x0 0x10>; + clocks = <&xtal>, + <&clkc CLKID_VID_PLL>, + /*the clock source is not supported now*/ + <&clkc CLKID_FCLK_DIV4>, + <&clkc CLKID_FCLK_DIV3>; + clock-names = "xtal", + "vid_pll_clk", + "fclk_div4", + "fclk_div3"; + clock-select = ,,,, + ,,,; + clock-select-new = ,,,, + ,,,, + ,,,, + ,,,; + /*all channels use the default clock source XTAL_CLK*/ + /*and you can shoose it in file dt-bindings/pwm/meson.h*/ + }; }; &efuse { diff --git a/arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts b/arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts index 988a8f0e34b0..7ad4094c6014 100644 --- a/arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts +++ b/arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts @@ -18,8 +18,6 @@ /dts-v1/; #include -#include -#include #include "mesongxl.dtsi" / { model = "Amlogic"; @@ -183,6 +181,36 @@ "usb1", "usb1_to_ddr"; }; + + pwm { + compatible = "amlogic, meson-pwm"; + status = "okay"; + pwm-outputs = ,,,, + ,,,; + pwm-outputs-new = ,,,, + ,,,, + ,,,, + ,,,; + reg = <0x0 0xc1108550 0x0 0x30>, + <0x0 0xc8100550 0x0 0x10>; + clocks = <&xtal>, + <&clkc CLKID_VID_PLL>, + /*the clock source is not supported now*/ + <&clkc CLKID_FCLK_DIV4>, + <&clkc CLKID_FCLK_DIV3>; + clock-names = "xtal", + "vid_pll_clk", + "fclk_div4", + "fclk_div3"; + clock-select = ,,,, + ,,,; + clock-select-new = ,,,, + ,,,, + ,,,, + ,,,; + /*all channels use the default clock source XTAL_CLK*/ + /*and you can shoose it in file dt-bindings/pwm/meson.h*/ + }; }; &efuse { diff --git a/arch/arm64/boot/dts/amlogic/gxm_q200_2g.dts b/arch/arm64/boot/dts/amlogic/gxm_q200_2g.dts index eb79ede0975d..ce2e0863052e 100644 --- a/arch/arm64/boot/dts/amlogic/gxm_q200_2g.dts +++ b/arch/arm64/boot/dts/amlogic/gxm_q200_2g.dts @@ -17,7 +17,6 @@ /dts-v1/; -#include #include "mesongxm.dtsi" #include #include @@ -180,6 +179,35 @@ "usb1_to_ddr"; }; + pwm { + compatible = "amlogic, meson-pwm"; + status = "okay"; + pwm-outputs = ,,,, + ,,,; + pwm-outputs-new = ,,,, + ,,,, + ,,,, + ,,,; + reg = <0x0 0xc1108550 0x0 0x30>, + <0x0 0xc8100550 0x0 0x10>; + clocks = <&xtal>, + <&clkc CLKID_VID_PLL>, + /*the clock source is not supported now*/ + <&clkc CLKID_FCLK_DIV4>, + <&clkc CLKID_FCLK_DIV3>; + clock-names = "xtal", + "vid_pll_clk", + "fclk_div4", + "fclk_div3"; + clock-select = ,,,, + ,,,; + clock-select-new = ,,,, + ,,,, + ,,,, + ,,,; + /*all channels use the default clock source XTAL_CLK*/ + /*and you can shoose it in file dt-bindings/pwm/meson.h*/ + }; }; &efuse { diff --git a/arch/arm64/boot/dts/amlogic/mesongxl.dtsi b/arch/arm64/boot/dts/amlogic/mesongxl.dtsi index c853f0846206..fbb9f0c80bdf 100644 --- a/arch/arm64/boot/dts/amlogic/mesongxl.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesongxl.dtsi @@ -18,6 +18,8 @@ #include #include #include +#include +#include / { cpus:cpus { #address-cells = <2>; diff --git a/arch/arm64/boot/dts/amlogic/mesongxm.dtsi b/arch/arm64/boot/dts/amlogic/mesongxm.dtsi index c9a71c9b9d5e..ba2880229fcc 100644 --- a/arch/arm64/boot/dts/amlogic/mesongxm.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesongxm.dtsi @@ -18,6 +18,9 @@ #include #include #include +#include +#include + / { cpus:cpus { #address-cells = <2>; diff --git a/arch/arm64/configs/meson64_defconfig b/arch/arm64/configs/meson64_defconfig index 75b8e2265657..384643736717 100644 --- a/arch/arm64/configs/meson64_defconfig +++ b/arch/arm64/configs/meson64_defconfig @@ -186,6 +186,7 @@ CONFIG_AMLOGIC_EFUSE=y CONFIG_AMLOGIC_REBOOT=y CONFIG_AMLOGIC_INTERNAL_PHY=y CONFIG_AMLOGIC_CPU_HOTPLUG=y +CONFIG_AMLOGIC_PWM=y CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" CONFIG_DEVTMPFS=y CONFIG_DEVTMPFS_MOUNT=y @@ -278,6 +279,7 @@ CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y CONFIG_DEVFREQ_GOV_PERFORMANCE=y CONFIG_DEVFREQ_GOV_POWERSAVE=y CONFIG_DEVFREQ_GOV_USERSPACE=y +CONFIG_PWM=y CONFIG_EXT2_FS=y CONFIG_EXT3_FS=y CONFIG_EXT3_FS_POSIX_ACL=y diff --git a/drivers/amlogic/Kconfig b/drivers/amlogic/Kconfig index e5aefce0eef1..f26257aef018 100644 --- a/drivers/amlogic/Kconfig +++ b/drivers/amlogic/Kconfig @@ -47,5 +47,7 @@ source "drivers/amlogic/ethernet/phy/Kconfig" source "drivers/amlogic/cpu_hotplug/Kconfig" +source "drivers/amlogic/pwm/Kconfig" + endmenu endif diff --git a/drivers/amlogic/Makefile b/drivers/amlogic/Makefile index b8a80c123c3f..50447905806f 100644 --- a/drivers/amlogic/Makefile +++ b/drivers/amlogic/Makefile @@ -42,3 +42,6 @@ obj-$(CONFIG_AMLOGIC_REBOOT) += reboot/ obj-$(CONFIG_AMLOGIC_INTERNAL_PHY) += ethernet/phy/ obj-$(CONFIG_AMLOGIC_CPU_HOTPLUG) += cpu_hotplug/ + +obj-$(CONFIG_AMLOGIC_PWM) += pwm/ + diff --git a/drivers/amlogic/pwm/Kconfig b/drivers/amlogic/pwm/Kconfig new file mode 100644 index 000000000000..6915b3361713 --- /dev/null +++ b/drivers/amlogic/pwm/Kconfig @@ -0,0 +1,12 @@ +# +# PWM configuration +# + + +menuconfig AMLOGIC_PWM + bool "Amlogic PWM driver" + depends on PWM_SYSFS + depends on PWM + default n + help + say y to enable Amlogic PWM driver. diff --git a/drivers/amlogic/pwm/Makefile b/drivers/amlogic/pwm/Makefile new file mode 100644 index 000000000000..1d90757b1945 --- /dev/null +++ b/drivers/amlogic/pwm/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for PWM +# + +obj-$(CONFIG_AMLOGIC_PWM) += pwm_meson.o pwm_meson_sysfs.o diff --git a/drivers/amlogic/pwm/pwm_meson.c b/drivers/amlogic/pwm/pwm_meson.c new file mode 100644 index 000000000000..6dbd4654b0d6 --- /dev/null +++ b/drivers/amlogic/pwm/pwm_meson.c @@ -0,0 +1,1018 @@ +/* + * drivers/amlogic/pwm/pwm_meson.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + */ + +#undef pr_fmt +#define pr_fmt(fmt) "pwm: " fmt + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static DEFINE_SPINLOCK(aml_pwm_lock); + + +void pwm_set_reg_bits(void __iomem *reg, + unsigned int mask, + const unsigned int val) +{ + unsigned int tmp, orig; + + orig = readl(reg); + tmp = orig & ~mask; + tmp |= val & mask; + writel(tmp, reg); +} + +void pwm_write_reg(void __iomem *reg, + const unsigned int val) +{ + unsigned int tmp, orig; + + orig = readl(reg); + tmp = orig & ~(0xffffffff); + tmp |= val; + writel(tmp, reg); +}; + +void pwm_clear_reg_bits(void __iomem *reg, const unsigned int val) +{ + unsigned int tmp, orig; + + orig = readl(reg); + tmp = orig & ~val; + writel(tmp, reg); +} + +void pwm_write_reg1(void __iomem *reg, const unsigned int val) +{ + unsigned int tmp = 0, orig; + + orig = readl(reg); + tmp = orig; + tmp |= val; + writel(tmp, reg); +}; + +struct aml_pwm_chip *to_aml_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct aml_pwm_chip, chip); +} + +static +struct aml_pwm_channel *pwm_aml_calc(struct aml_pwm_chip *chip, + struct pwm_device *pwm, + unsigned int duty_ns, + unsigned int period_ns, + struct clk *clk) +{ + struct aml_pwm_channel *our_chan = pwm_get_chip_data(pwm); + unsigned int fout_freq = 0, pwm_pre_div = 0; + unsigned int i = 0; + unsigned long temp = 0; + unsigned long pwm_cnt = 0; + unsigned long rate = 0; + unsigned int pwm_freq; + unsigned long freq_div; + + if ((duty_ns < 0) || (duty_ns > period_ns)) { + dev_err(chip->chip.dev, "Not available duty error!\n"); + return NULL; + } + + if (!IS_ERR(clk)) + rate = clk_get_rate(clk); + + pwm_freq = NSEC_PER_SEC / period_ns; + + fout_freq = ((pwm_freq >= ((rate/1000) * 500)) ? + ((rate/1000) * 500) : pwm_freq); + for (i = 0; i < 0x7f; i++) { + pwm_pre_div = i; + freq_div = rate / (pwm_pre_div + 1); + if (freq_div < pwm_freq) + continue; + pwm_cnt = freq_div / pwm_freq; + if (pwm_cnt <= 0xffff) + break; + } + + our_chan->pwm_pre_div = pwm_pre_div; + if (duty_ns == 0) { + our_chan->pwm_hi = 0; + our_chan->pwm_lo = pwm_cnt; + return our_chan; + } else if (duty_ns == period_ns) { + our_chan->pwm_hi = pwm_cnt; + our_chan->pwm_lo = 0; + return our_chan; + } + + temp = (unsigned long)(pwm_cnt * duty_ns); + temp /= period_ns; + + our_chan->pwm_hi = (unsigned int)temp-1; + our_chan->pwm_lo = pwm_cnt - (unsigned int)temp-1; + our_chan->pwm_freq = pwm_freq; + + return our_chan; + +} + +static int pwm_aml_request(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct aml_pwm_chip *aml_chip = to_aml_pwm_chip(chip); + struct aml_pwm_channel *our_chan; + + if (cpu_after_eq(MESON_CPU_MAJOR_ID_GXTVBB)) { + if (!(aml_chip->variant.output_mask_new & BIT(pwm->hwpwm))) { + dev_warn(chip->dev, + "tried to request PWM channel %d without output\n", + pwm->hwpwm); + return -EINVAL; + } + } else { + if (!(aml_chip->variant.output_mask & BIT(pwm->hwpwm))) { + dev_warn(chip->dev, + "tried to request PWM channel %d without output\n", + pwm->hwpwm); + return -EINVAL; + } + } + our_chan = devm_kzalloc(chip->dev, sizeof(*our_chan), GFP_KERNEL); + if (!our_chan) + return -ENOMEM; + + pwm_set_chip_data(pwm, our_chan); + + return 0; +} + +static void pwm_aml_free(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + devm_kfree(chip->dev, pwm_get_chip_data(pwm)); + pwm_set_chip_data(pwm, NULL); +} + +static int pwm_gxbb_enable(struct aml_pwm_chip *aml_chip, + unsigned int id) +{ + void __iomem *reg; + unsigned int mask = 0; + unsigned int val; + + switch (id) { + case PWM_A: + reg = aml_chip->base + REG_MISC_AB; + val = 1 << 0; + break; + case PWM_B: + reg = aml_chip->base + REG_MISC_AB; + val = 1 << 1; + break; + case PWM_C: + reg = aml_chip->base + REG_MISC_CD; + val = 1 << 0; + break; + case PWM_D: + reg = aml_chip->base + REG_MISC_CD; + val = 1 << 1; + break; + case PWM_E: + reg = aml_chip->base + REG_MISC_EF; + val = 1 << 0; + break; + case PWM_F: + reg = aml_chip->base + REG_MISC_EF; + val = 1 << 1; + break; + case PWM_AO_A: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + val = 1 << 0; + break; + case PWM_AO_B: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + val = 1 << 1; + break; + default: + dev_err(aml_chip->chip.dev, + "enable,index is not legal\n"); + return -EINVAL; + + break; + } + pwm_set_reg_bits(reg, mask, val); + + return 0; +} + +/* + *do it for hardware defect, + * PWM_A and PWM_B enable bit should be setted together. + */ +static int pwm_gxtvbb_enable(struct aml_pwm_chip *aml_chip, + unsigned int id) +{ + void __iomem *reg; + unsigned int mask = 0; + unsigned int val; + + switch (id) { + case PWM_A: + case PWM_B: + reg = aml_chip->base + REG_MISC_AB; + val = 0x3 << 0; + break; + case PWM_C: + case PWM_D: + reg = aml_chip->base + REG_MISC_CD; + val = 0x3 << 0; + break; + case PWM_E: + case PWM_F: + reg = aml_chip->base + REG_MISC_EF; + val = 0x3 << 0; + break; + case PWM_AO_A: + case PWM_AO_B: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + val = 0x3 << 0; + break; + case PWM_A2: + reg = aml_chip->base + REG_MISC_AB; + val = 1 << 25; + break; + case PWM_B2: + reg = aml_chip->base + REG_MISC_AB; + val = 1 << 24; + break; + case PWM_C2: + reg = aml_chip->base + REG_MISC_CD; + val = 1 << 25; + break; + case PWM_D2: + reg = aml_chip->base + REG_MISC_CD; + val = 1 << 24; + break; + case PWM_E2: + reg = aml_chip->base + REG_MISC_EF; + val = 1 << 25; + break; + case PWM_F2: + reg = aml_chip->base + REG_MISC_EF; + val = 1 << 24; + break; + case PWM_AO_A2: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + val = 1 << 25; + break; + case PWM_AO_B2: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + val = 1 << 24; + break; + default: + dev_err(aml_chip->chip.dev, + "enable,index is not legal\n"); + return -EINVAL; + + break; + } + pwm_set_reg_bits(reg, mask, val); + + return 0; + +} + +static int pwm_txl_enable(struct aml_pwm_chip *aml_chip, + unsigned int id) +{ + void __iomem *reg; + unsigned int mask = 0; + unsigned int val; + + switch (id) { + case PWM_A: + reg = aml_chip->base + REG_MISC_AB; + val = 1 << 0; + break; + case PWM_B: + reg = aml_chip->base + REG_MISC_AB; + val = 1 << 1; + break; + case PWM_C: + reg = aml_chip->base + REG_MISC_CD; + val = 1 << 0; + break; + case PWM_D: + reg = aml_chip->base + REG_MISC_CD; + val = 1 << 1; + break; + case PWM_E: + reg = aml_chip->base + REG_MISC_EF; + val = 1 << 0; + break; + case PWM_F: + reg = aml_chip->base + REG_MISC_EF; + val = 1 << 1; + break; + case PWM_AO_A: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + val = 1 << 1; + break; + case PWM_AO_B: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + val = 1 << 1; + case PWM_A2: + reg = aml_chip->base + REG_MISC_AB; + val = 1 << 25; + break; + case PWM_B2: + reg = aml_chip->base + REG_MISC_AB; + val = 1 << 24; + break; + case PWM_C2: + reg = aml_chip->base + REG_MISC_CD; + val = 1 << 25; + break; + case PWM_D2: + reg = aml_chip->base + REG_MISC_CD; + val = 1 << 24; + break; + case PWM_E2: + reg = aml_chip->base + REG_MISC_EF; + val = 1 << 25; + break; + case PWM_F2: + reg = aml_chip->base + REG_MISC_EF; + val = 1 << 24; + break; + case PWM_AO_A2: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + val = 1 << 25; + break; + case PWM_AO_B2: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + val = 1 << 24; + break; + default: + dev_err(aml_chip->chip.dev, + "enable,index is not legal\n"); + return -EINVAL; + + break; + } + pwm_set_reg_bits(reg, mask, val); + + return 0; +} + + +static int pwm_aml_enable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct aml_pwm_chip *aml_chip = to_aml_pwm_chip(chip); + unsigned int id = pwm->hwpwm; + unsigned long flags; + unsigned int soc_id = get_cpu_type(); + + spin_lock_irqsave(&aml_pwm_lock, flags); + switch (soc_id) { + case MESON_CPU_MAJOR_ID_GXBB: + pwm_gxbb_enable(aml_chip, id); + break; + case MESON_CPU_MAJOR_ID_GXTVBB: + pwm_gxtvbb_enable(aml_chip, id); + break; + case MESON_CPU_MAJOR_ID_GXL: + case MESON_CPU_MAJOR_ID_GXM: + case MESON_CPU_MAJOR_ID_TXL: + pwm_txl_enable(aml_chip, id); + break; + default: + dev_err(chip->dev, "not support\n"); + break; + } + spin_unlock_irqrestore(&aml_pwm_lock, flags); + + return 0; +} + +static void pwm_aml_disable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + void __iomem *reg = NULL; + struct aml_pwm_chip *aml_chip = to_aml_pwm_chip(chip); + unsigned int id = pwm->hwpwm; + unsigned long flags; + unsigned int mask = 0; + unsigned int val; + + spin_lock_irqsave(&aml_pwm_lock, flags); + switch (id) { + case PWM_A: + reg = aml_chip->base + REG_MISC_AB; + mask = 1 << 0; + val = 0 << 0; + break; + case PWM_B: + reg = aml_chip->base + REG_MISC_AB; + mask = 1 << 1; + val = 0 << 1; + break; + case PWM_C: + reg = aml_chip->base + REG_MISC_CD; + mask = 1 << 0; + val = 0 << 0; + break; + case PWM_D: + reg = aml_chip->base + REG_MISC_CD; + mask = 1 << 1; + val = 0 << 1; + break; + case PWM_E: + reg = aml_chip->base + REG_MISC_EF; + mask = 1 << 0; + val = 0 << 0; + break; + case PWM_F: + reg = aml_chip->base + REG_MISC_EF; + mask = 1 << 1; + val = 0 << 1; + break; + case PWM_AO_A: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + mask = 1 << 0; + val = 0 << 0; + break; + case PWM_AO_B: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + mask = 1 << 1; + val = 0 << 1; + break; + default: + dev_err(aml_chip->chip.dev, + "disable,index is not legal\n"); + break; + } + pwm_set_reg_bits(reg, mask, val); + spin_unlock_irqrestore(&aml_pwm_lock, flags); +} + +static int pwm_aml_clk(struct aml_pwm_chip *aml_chip, + struct pwm_device *pwm, + unsigned int duty_ns, + unsigned int period_ns, + unsigned int offset) +{ + struct aml_pwm_channel *our_chan = pwm_get_chip_data(pwm); + struct clk *clk; + + switch ((aml_chip->clk_mask >> offset)&0x3) { + case 0x0: + clk = aml_chip->xtal_clk; + break; + case 0x1: + clk = aml_chip->vid_pll_clk; + break; + case 0x2: + clk = aml_chip->fclk_div4_clk; + break; + case 0x3: + clk = aml_chip->fclk_div3_clk; + break; + default: + clk = aml_chip->xtal_clk; + break; + } + + our_chan = pwm_aml_calc(aml_chip, pwm, duty_ns, period_ns, clk); + if (our_chan == NULL) + return -EINVAL; + + return 0; +} + +/* + * 8 base channels configuration for gxbb£¬gxtvbb and txl + */ +static int pwm_meson_config(struct aml_pwm_chip *aml_chip, + struct aml_pwm_channel *our_chan, + unsigned int id) +{ + void __iomem *misc_reg; + void __iomem *duty_reg; + unsigned int clk_source_mask; + unsigned int clk_source_val; + unsigned int clk_mask; + unsigned int clk_val; + unsigned int duty_val = + (our_chan->pwm_hi << 16) | (our_chan->pwm_lo); + + switch (id) { + case PWM_A: + misc_reg = aml_chip->base + REG_MISC_AB; + clk_source_mask = 0x3 << 4; + clk_source_val = ((aml_chip->clk_mask)&0x3) << 4; + clk_mask = (0x7f << 8) | (1 << 15); + clk_val = (our_chan->pwm_pre_div << 8) | (1 << 15); + duty_reg = aml_chip->base + REG_PWM_A; + break; + case PWM_B: + misc_reg = aml_chip->base + REG_MISC_AB; + clk_source_mask = 0x3 << 6; + clk_source_val = ((aml_chip->clk_mask >> 2)&0x3) << 6; + clk_mask = (0x7f << 16)|(1 << 23); + clk_val = (our_chan->pwm_pre_div << 16)|(1 << 23); + duty_reg = aml_chip->base + REG_PWM_B; + break; + case PWM_C: + misc_reg = aml_chip->base + REG_MISC_CD; + clk_source_mask = 0x3 << 4; + clk_source_val = ((aml_chip->clk_mask >> 4)&0x3) << 4; + clk_mask = (0x7f << 8)|(1 << 15); + clk_val = (our_chan->pwm_pre_div << 8)|(1 << 15); + duty_reg = aml_chip->base + REG_PWM_C; + break; + case PWM_D: + misc_reg = aml_chip->base + REG_MISC_CD; + clk_source_mask = 0x3 << 6; + clk_source_val = ((aml_chip->clk_mask >> 6)&0x3) << 6; + clk_mask = (0x7f << 16)|(1 << 23); + clk_val = (our_chan->pwm_pre_div << 16)|(1 << 23); + duty_reg = aml_chip->base + REG_PWM_D; + break; + case PWM_E: + misc_reg = aml_chip->base + REG_MISC_EF; + clk_source_mask = 0x3 << 4; + clk_source_val = ((aml_chip->clk_mask >> 8)&0x3) << 4; + clk_mask = (0x7f << 8)|(1 << 15); + clk_val = (our_chan->pwm_pre_div << 8)|(1 << 15); + duty_reg = aml_chip->base + REG_PWM_E; + break; + case PWM_F: + misc_reg = aml_chip->base + REG_MISC_EF; + clk_source_mask = 0x3 << 6; + clk_source_val = ((aml_chip->clk_mask >> 10)&0x3) << 6; + clk_mask = (0x7f << 16)|(1 << 23); + clk_val = (our_chan->pwm_pre_div << 16)|(1 << 23); + duty_reg = aml_chip->base + REG_PWM_F; + break; + case PWM_AO_A: + misc_reg = aml_chip->base + REG_MISC_AO_AB; + clk_source_mask = 0x3 << 4; + clk_source_val = ((aml_chip->clk_mask >> 12)&0x3) << 4; + clk_mask = (0x7f << 8)|(1 << 15); + clk_val = (our_chan->pwm_pre_div << 8)|(1 << 15); + duty_reg = aml_chip->base + REG_PWM_AO_A; + break; + case PWM_AO_B: + misc_reg = aml_chip->base + REG_MISC_AO_AB; + clk_source_mask = 0x3 << 6; + clk_source_val = ((aml_chip->clk_mask >> 14)&0x3) << 6; + clk_mask = (0x7f << 16)|(1 << 23); + clk_val = (our_chan->pwm_pre_div << 16)|(1 << 23); + duty_reg = aml_chip->base + REG_PWM_AO_B; + break; + default: + dev_err(aml_chip->chip.dev, + "config,index is not legal\n"); + return -EINVAL; + + break; + } + pwm_set_reg_bits(misc_reg, clk_source_mask, clk_source_val); + pwm_set_reg_bits(misc_reg, clk_mask, clk_val); + pwm_write_reg(duty_reg, duty_val); + + return 0; +} + +/* + * Additional 8 channels configuration for txl + */ +static int pwm_meson_config_ext(struct aml_pwm_chip *aml_chip, + struct aml_pwm_channel *our_chan, + unsigned int id) +{ + void __iomem *misc_reg; + void __iomem *duty_reg; + unsigned int clk_source_mask; + unsigned int clk_source_val; + unsigned int clk_mask; + unsigned int clk_val; + unsigned int duty_val = + (our_chan->pwm_hi << 16) | (our_chan->pwm_lo); + + switch (id) { + case PWM_A2: + misc_reg = aml_chip->base + REG_MISC_AB; + clk_source_mask = 0x3 << 4; + clk_source_val = ((aml_chip->clk_mask)&0x3) << 4; + clk_mask = (0x7f << 8) | (1 << 15); + clk_val = (our_chan->pwm_pre_div << 8) | (1 << 15); + duty_reg = aml_chip->base + REG_PWM_A2; + break; + case PWM_B2: + misc_reg = aml_chip->base + REG_MISC_AB; + clk_source_mask = 0x3 << 6; + clk_source_val = ((aml_chip->clk_mask >> 2)&0x3) << 6; + clk_mask = (0x7f << 16)|(1 << 23); + clk_val = (our_chan->pwm_pre_div << 16)|(1 << 23); + duty_reg = aml_chip->base + REG_PWM_B2; + break; + case PWM_C2: + misc_reg = aml_chip->base + REG_MISC_CD; + clk_source_mask = 0x3 << 4; + clk_source_val = ((aml_chip->clk_mask >> 4)&0x3) << 4; + clk_mask = (0x7f << 8)|(1 << 15); + clk_val = (our_chan->pwm_pre_div << 8)|(1 << 15); + duty_reg = aml_chip->base + REG_PWM_C2; + break; + case PWM_D2: + misc_reg = aml_chip->base + REG_MISC_CD; + clk_source_mask = 0x3 << 6; + clk_source_val = ((aml_chip->clk_mask >> 6)&0x3) << 6; + clk_mask = (0x7f << 16)|(1 << 23); + clk_val = (our_chan->pwm_pre_div << 16)|(1 << 23); + duty_reg = aml_chip->base + REG_PWM_D2; + break; + case PWM_E2: + misc_reg = aml_chip->base + REG_MISC_EF; + clk_source_mask = 0x3 << 4; + clk_source_val = ((aml_chip->clk_mask >> 8)&0x3) << 4; + clk_mask = (0x7f << 8)|(1 << 15); + clk_val = (our_chan->pwm_pre_div << 8)|(1 << 15); + duty_reg = aml_chip->base + REG_PWM_E2; + break; + case PWM_F2: + misc_reg = aml_chip->base + REG_MISC_EF; + clk_source_mask = 0x3 << 6; + clk_source_val = ((aml_chip->clk_mask >> 10)&0x3) << 6; + clk_mask = (0x7f << 16)|(1 << 23); + clk_val = (our_chan->pwm_pre_div << 16)|(1 << 23); + duty_reg = aml_chip->base + REG_PWM_F2; + break; + case PWM_AO_A2: + misc_reg = aml_chip->base + REG_MISC_AO_AB; + clk_source_mask = 0x3 << 4; + clk_source_val = ((aml_chip->clk_mask >> 12)&0x3) << 4; + clk_mask = (0x7f << 8)|(1 << 15); + clk_val = (our_chan->pwm_pre_div << 8)|(1 << 15); + duty_reg = aml_chip->base + REG_PWM_AO_A2; + break; + case PWM_AO_B2: + misc_reg = aml_chip->base + REG_MISC_AO_AB; + clk_source_mask = 0x3 << 6; + clk_source_val = ((aml_chip->clk_mask >> 14)&0x3) << 6; + clk_mask = (0x7f << 16)|(1 << 23); + clk_val = (our_chan->pwm_pre_div << 16)|(1 << 23); + duty_reg = aml_chip->base + REG_PWM_AO_B2; + break; + default: + dev_err(aml_chip->chip.dev, + "congig_ext,index is not legal\n"); + return -EINVAL; + + break; + } + pwm_set_reg_bits(misc_reg, clk_source_mask, clk_source_val); + pwm_set_reg_bits(misc_reg, clk_mask, clk_val); + pwm_write_reg(duty_reg, duty_val); + + return 0; +} + +static int pwm_aml_config(struct pwm_chip *chip, + struct pwm_device *pwm, + int duty_ns, + int period_ns) +{ + struct aml_pwm_chip *aml_chip = to_aml_pwm_chip(chip); + struct aml_pwm_channel *our_chan = pwm_get_chip_data(pwm); + unsigned int id = pwm->hwpwm; + unsigned int offset; + int ret; + + if ((~(aml_chip->inverter_mask >> id) & 0x1)) + duty_ns = period_ns - duty_ns; + + if (period_ns > NSEC_PER_SEC) + return -ERANGE; + + if (period_ns == our_chan->period_ns && duty_ns == our_chan->duty_ns) + return 0; + + offset = id * 2; + ret = pwm_aml_clk(aml_chip, pwm, duty_ns, period_ns, offset); + if (ret) { + dev_err(chip->dev, "tried to calc pwm freq err\n"); + return -EINVAL; + } + + if (id < AML_PWM_NUM) + pwm_meson_config(aml_chip, our_chan, id); + else + pwm_meson_config_ext(aml_chip, our_chan, id); + + our_chan->period_ns = period_ns; + our_chan->duty_ns = duty_ns; + + return 0; +} + +static void pwm_aml_set_invert(struct pwm_chip *chip, struct pwm_device *pwm, + unsigned int channel, bool invert) +{ + struct aml_pwm_chip *aml_chip = to_aml_pwm_chip(chip); + unsigned long flags; + struct aml_pwm_channel *our_chan = pwm_get_chip_data(pwm); + + spin_lock_irqsave(&aml_pwm_lock, flags); + if (invert) + aml_chip->inverter_mask |= BIT(channel); + else + aml_chip->inverter_mask &= ~BIT(channel); + + pwm_aml_config(chip, pwm, our_chan->duty_ns, + our_chan->period_ns); + + spin_unlock_irqrestore(&aml_pwm_lock, flags); +} + + +static int pwm_aml_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + bool invert = (polarity == PWM_POLARITY_NORMAL); + + /* Inverted means normal in the hardware. */ + pwm_aml_set_invert(chip, pwm, pwm->hwpwm, invert); + + return 0; +} + +static const struct pwm_ops pwm_aml_ops = { + .request = pwm_aml_request, + .free = pwm_aml_free, + .enable = pwm_aml_enable, + .disable = pwm_aml_disable, + .config = pwm_aml_config, + .set_polarity = pwm_aml_set_polarity, + .owner = THIS_MODULE, +}; + +#ifdef CONFIG_OF +static const struct of_device_id aml_pwm_matches[] = { + { .compatible = "amlogic, meson-pwm", }, + {}, +}; + +static int pwm_aml_parse_dt(struct aml_pwm_chip *chip) +{ + struct device_node *np = chip->chip.dev->of_node; + const struct of_device_id *match; + int i = 0; + struct property *prop; + const __be32 *cur; + u32 val; + + match = of_match_node(aml_pwm_matches, np); + if (!match) + return -ENODEV; + + chip->base = of_iomap(chip->chip.dev->of_node, 0); + if (IS_ERR(chip->base)) + return PTR_ERR(chip->base); + chip->ao_base = of_iomap(chip->chip.dev->of_node, 1); + if (IS_ERR(chip->ao_base)) + return PTR_ERR(chip->ao_base); + + if (cpu_after_eq(MESON_CPU_MAJOR_ID_GXTVBB)) { + of_property_for_each_u32(np, + "pwm-outputs-new", + prop, + cur, + val) { + if (val >= AML_PWM_NUM_NEW) { + dev_err(chip->chip.dev, + "%s: invalid channel index in pwm-outputs-new property\n", + __func__); + continue; + } + chip->variant.output_mask_new |= BIT(val); + } + } else { + of_property_for_each_u32(np, "pwm-outputs", prop, cur, val) { + if (val >= AML_PWM_NUM) { + dev_err(chip->chip.dev, + "%s: invalid channel index in pwm-outputs property\n", + __func__); + continue; + } + chip->variant.output_mask |= BIT(val); + } + } + chip->xtal_clk = clk_get(chip->chip.dev, "xtal"); + chip->vid_pll_clk = clk_get(chip->chip.dev, "vid_pll_clk"); + chip->fclk_div4_clk = clk_get(chip->chip.dev, "fclk_div4"); + chip->fclk_div3_clk = clk_get(chip->chip.dev, "fclk_div3"); + + if (cpu_after_eq(MESON_CPU_MAJOR_ID_GXTVBB)) { + of_property_for_each_u32(np, + "clock-select-new", + prop, + cur, + val) { + if (val >= AML_PWM_NUM_NEW) { + dev_err(chip->chip.dev, + "%s: invalid channel index in clock-select-new property\n", + __func__); + continue; + } + chip->clk_mask |= val<<(2 * i); + i++; + } + } else { + of_property_for_each_u32(np, "clock-select", prop, cur, val) { + if (val >= AML_PWM_NUM) { + dev_err(chip->chip.dev, + "%s: invalid channel index in clock-select property\n", + __func__); + continue; + } + chip->clk_mask |= val<<(2 * i); + i++; + } + } + return 0; +} +#else +static int pwm_aml_parse_dt(struct aml_pwm_chip *chip) +{ + return -ENODEV; +} +#endif + +static int pwm_aml_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct aml_pwm_chip *chip; + int ret; + int ret_fs; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + chip->chip.dev = &pdev->dev; + chip->chip.ops = &pwm_aml_ops; + chip->chip.base = -1; + /*add for four new node*/ + chip->variant.constant = 0; + chip->variant.blink_enable = 0; + chip->variant.blink_times = 0; + chip->variant.times = 0; + + if (cpu_after_eq(MESON_CPU_MAJOR_ID_GXTVBB)) + chip->chip.npwm = AML_PWM_NUM_NEW; + else + chip->chip.npwm = AML_PWM_NUM; + chip->inverter_mask = BIT(AML_PWM_NUM) - 1; + /*chip->inverter_mask = BIT_ULL(AML_PWM_NUM) - 1;*/ + + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { + ret = pwm_aml_parse_dt(chip); + if (ret) + return ret; + } else { + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform data specified\n"); + return -EINVAL; + } + memcpy(&chip->variant, pdev->dev.platform_data, + sizeof(chip->variant)); + } + + ret = pwmchip_add(&chip->chip); + if (ret < 0) { + dev_err(dev, "failed to register PWM chip\n"); + return ret; + } + + if (cpu_after_eq(MESON_CPU_MAJOR_ID_GXTVBB)) { + ret_fs = meson_pwm_sysfs_init(dev); + if (ret_fs) { + dev_err(dev, "pwm sysfs group creation failed\n"); + return ret_fs; + } + } + platform_set_drvdata(pdev, chip); + + return 0; +} + +static int pwm_aml_remove(struct platform_device *pdev) +{ + struct aml_pwm_chip *chip = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&chip->chip); + if (ret < 0) + return ret; + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pwm_aml_suspend(struct device *dev) +{ + struct aml_pwm_chip *chip = dev_get_drvdata(dev); + unsigned int i; + + /* + * No one preserves these values during suspend so reset them. + * Otherwise driver leaves PWM unconfigured if same values are + * passed to pwm_config() next time. + */ + + for (i = 0; i < AML_PWM_NUM; ++i) { + struct pwm_device *pwm = &chip->chip.pwms[i]; + struct aml_pwm_channel *chan = pwm_get_chip_data(pwm); + + if (!chan) + continue; + + chan->period_ns = 0; + chan->duty_ns = 0; + } + + return 0; +} + +static int pwm_aml_resume(struct device *dev) +{ + return 0; +} +#endif + +static const struct dev_pm_ops pwm_aml_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pwm_aml_suspend, pwm_aml_resume) +}; + +static struct platform_driver pwm_aml_driver = { + .driver = { + .name = "meson-pwm", + .owner = THIS_MODULE, + .pm = &pwm_aml_pm_ops, + .of_match_table = of_match_ptr(aml_pwm_matches), + }, + .probe = pwm_aml_probe, + .remove = pwm_aml_remove, +}; +/* + *need to register before wifi_dt driver + */ +static int __init aml_pwm_init(void) +{ + int ret; + + ret = platform_driver_register(&pwm_aml_driver); + return ret; +} +static void __exit aml_pwm_exit(void) +{ + platform_driver_unregister(&pwm_aml_driver); +} +fs_initcall_sync(aml_pwm_init); +module_exit(aml_pwm_exit); + +MODULE_ALIAS("platform:meson-pwm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/amlogic/pwm/pwm_meson_sysfs.c b/drivers/amlogic/pwm/pwm_meson_sysfs.c new file mode 100644 index 000000000000..104cab5adbda --- /dev/null +++ b/drivers/amlogic/pwm/pwm_meson_sysfs.c @@ -0,0 +1,697 @@ +/* + * drivers/amlogic/pwm/pwm_meson_sysfs.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + */ + +#undef pr_fmt +#define pr_fmt(fmt) "pwm: " fmt + +#include +#include +#include +#include +#include +#include + + +/** + * pwm_constant_enable() + * - start a constant PWM output toggling + * txl only support 8 channel constant output + * @chip: aml_pwm_chip struct + * @index: pwm channel to choose,like PWM_A or PWM_B + */ +int pwm_constant_enable(struct aml_pwm_chip *chip, int index) +{ + struct aml_pwm_chip *aml_chip = chip; + int id = index; + void __iomem *reg; + unsigned int mask = 0; + unsigned int val; + + if ((id < 0) && (id > 7)) { + dev_err(aml_chip->chip.dev, + "constant,index is not within the scope!\n"); + return -EINVAL; + } + + switch (id) { + case PWM_A: + reg = aml_chip->base + REG_MISC_AB; + val = 1 << 28; + break; + case PWM_B: + reg = aml_chip->base + REG_MISC_AB; + val = 1 << 29; + break; + case PWM_C: + reg = aml_chip->base + REG_MISC_CD; + val = 1 << 28; + break; + case PWM_D: + reg = aml_chip->base + REG_MISC_CD; + val = 1 << 29; + break; + case PWM_E: + reg = aml_chip->base + REG_MISC_EF; + val = 1 << 28; + break; + case PWM_F: + reg = aml_chip->base + REG_MISC_EF; + val = 1 << 29; + break; + case PWM_AO_A: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + val = 1 << 28; + break; + case PWM_AO_B: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + val = 1 << 29; + break; + default: + dev_err(aml_chip->chip.dev, + "constant,index is not legal\n"); + return -EINVAL; + break; + } + pwm_set_reg_bits(reg, mask, val); + + return 0; +} +EXPORT_SYMBOL_GPL(pwm_constant_enable); + + +/** + * pwm_constant_disable() - stop a constant PWM output toggling + * @chip: aml_pwm_chip struct + * @index: pwm channel to choose,like PWM_A or PWM_B + */ + +int pwm_constant_disable(struct aml_pwm_chip *chip, int index) +{ + struct aml_pwm_chip *aml_chip = chip; + int id = index; + void __iomem *reg; + unsigned int mask; + unsigned int val; + + if ((id < 0) && (id > 7)) { + dev_err(aml_chip->chip.dev, + "constant,index is not within the scope!\n"); + return -EINVAL; + } + + switch (id) { + case PWM_A: + reg = aml_chip->base + REG_MISC_AB; + mask = 1 << 28; + val = 0 << 28; + break; + case PWM_B: + reg = aml_chip->base + REG_MISC_AB; + mask = 1 << 29; + val = 0 << 29; + break; + case PWM_C: + reg = aml_chip->base + REG_MISC_CD; + mask = 1 << 28; + val = 0 << 28; + break; + case PWM_D: + reg = aml_chip->base + REG_MISC_CD; + mask = 1 << 29; + val = 0 << 29; + break; + case PWM_E: + reg = aml_chip->base + REG_MISC_EF; + mask = 1 << 28; + val = 0 << 28; + break; + case PWM_F: + reg = aml_chip->base + REG_MISC_EF; + mask = 1 << 29; + val = 0 << 29; + break; + case PWM_AO_A: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + mask = 1 << 28; + val = 0 << 28; + break; + case PWM_AO_B: + reg = aml_chip->ao_base + REG_MISC_AO_AB; + mask = 1 << 29; + val = 0 << 29; + break; + default: + dev_err(aml_chip->chip.dev, + "constant,index is not legal\n"); + return -EINVAL; + + break; + } + pwm_set_reg_bits(reg, mask, val); + return 0; +} +EXPORT_SYMBOL_GPL(pwm_constant_disable); + + +static ssize_t pwm_constant_show(struct device *child, + struct device_attribute *attr, char *buf) +{ + struct aml_pwm_chip *chip = + (struct aml_pwm_chip *)dev_get_drvdata(child); + return sprintf(buf, "%d\n", chip->variant.constant); +} + +static ssize_t pwm_constant_store(struct device *child, + struct device_attribute *attr, + const char *buf, size_t size) + +{ + struct aml_pwm_chip *chip = + (struct aml_pwm_chip *)dev_get_drvdata(child); + int val, ret, id, res; + + res = sscanf(buf, "%d %d", &val, &id); + if (res != 2) { + dev_err(child, "Can't parse pwm id,usage:[value index]\n"); + return -EINVAL; + } + if ((id < 0) && (id > 7)) { + dev_err(chip->chip.dev, + "constant,index is not within the scope!\n"); + return -EINVAL; + } + + switch (val) { + case 0: + ret = pwm_constant_disable(chip, id); + chip->variant.constant = 0; + break; + case 1: + ret = pwm_constant_enable(chip, id); + chip->variant.constant = 1; + break; + default: + ret = -EINVAL; + break; + } + + return ret ? : size; +} + +/** + * pwm_set_times() - set PWM times output toggling + * set pwm a1 and pwm a2 timer together + * and pwm a1 should be set first + * @chip: aml_pwm_chip struct + * @index: pwm channel to choose,like PWM_A or PWM_B,range from 1 to 15 + * @value: blink times to set,range from 1 to 255 + */ + +int pwm_set_times(struct aml_pwm_chip *chip, + int index, int value) +{ + struct aml_pwm_chip *aml_chip = chip; + int id = index; + int val = value; + void __iomem *reg; + unsigned int clear_val; + unsigned int set_val; + + if (((val <= 0) && (val > 255)) || ((id < 0) && (id > 15))) { + dev_err(aml_chip->chip.dev, + "index or value is not within the scope!\n"); + return -EINVAL; + } + + switch (id) { + case PWM_A: + reg = aml_chip->base + REG_TIME_AB; + clear_val = 0xff << 24; + set_val = val << 24; + break; + case PWM_B: + reg = aml_chip->base + REG_TIME_AB; + clear_val = 0xff << 8; + set_val = val << 8; + break; + case PWM_C: + reg = aml_chip->base + REG_TIME_CD; + clear_val = 0xff << 24; + set_val = val << 24; + break; + case PWM_D: + reg = aml_chip->base + REG_TIME_CD; + clear_val = 0xff << 8; + set_val = val << 8; + break; + case PWM_E: + reg = aml_chip->base + REG_TIME_EF; + clear_val = 0xff << 24; + set_val = val << 24; + break; + case PWM_F: + reg = aml_chip->base + REG_TIME_EF; + clear_val = 0xff << 8; + set_val = val << 8; + break; + case PWM_AO_A: + reg = aml_chip->ao_base + REG_TIME_AO_AB; + clear_val = 0xff << 24; + set_val = val << 24; + break; + case PWM_AO_B: + reg = aml_chip->ao_base + REG_TIME_AO_AB; + clear_val = 0xff << 8; + set_val = val << 8; + break; + case PWM_A2: + reg = aml_chip->base + REG_TIME_AB; + clear_val = 0xff << 16; + set_val = val << 16; + break; + case PWM_B2: + reg = aml_chip->base + REG_TIME_AB; + clear_val = 0xff; + set_val = val; + break; + case PWM_C2: + reg = aml_chip->base + REG_TIME_CD; + clear_val = 0xff << 16; + set_val = val << 16; + break; + case PWM_D2: + reg = aml_chip->base + REG_TIME_CD; + clear_val = 0xff; + set_val = val; + break; + case PWM_E2: + reg = aml_chip->base + REG_TIME_EF; + clear_val = 0xff << 16; + set_val = val << 16; + break; + case PWM_F2: + reg = aml_chip->base + REG_TIME_EF; + clear_val = 0xff; + set_val = val; + break; + case PWM_AO_A2: + reg = aml_chip->ao_base + REG_TIME_AO_AB; + clear_val = 0xff << 16; + set_val = val << 16; + break; + case PWM_AO_B2: + reg = aml_chip->ao_base + REG_TIME_AO_AB; + clear_val = 0xff; + set_val = val; + break; + default: + dev_err(aml_chip->chip.dev, + "times,index is not legal\n"); + return -EINVAL; + + break; + } + pwm_clear_reg_bits(reg, clear_val); + pwm_write_reg1(reg, set_val); + + return 0; +} +EXPORT_SYMBOL_GPL(pwm_set_times); + +static ssize_t pwm_times_show(struct device *child, + struct device_attribute *attr, char *buf) +{ + struct aml_pwm_chip *chip = + (struct aml_pwm_chip *)dev_get_drvdata(child); + return sprintf(buf, "%d\n", chip->variant.times); +} + +static ssize_t pwm_times_store(struct device *child, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct aml_pwm_chip *chip = + (struct aml_pwm_chip *)dev_get_drvdata(child); + int val, ret, id, res; + + res = sscanf(buf, "%d %d", &val, &id); + if (res != 2) { + dev_err(child, + "Can't parse pwm id and value,usage:[value index]\n"); + return -EINVAL; + } + if (((val <= 0) && (val > 255)) || ((id < 0) && (id > 15))) { + dev_err(chip->chip.dev, + "index or value is not within the scope!\n"); + return -EINVAL; + } + + ret = pwm_set_times(chip, id, val); + chip->variant.times = val; + + return ret ? : size; +} + +/** + * pwm_blink_enable() + * - start a blink PWM output toggling + * txl only support 8 channel blink output + * @chip: aml_pwm_chip struct + * @index: pwm channel to choose,like PWM_A or PWM_B + */ +int pwm_blink_enable(struct aml_pwm_chip *chip, int index) +{ + struct aml_pwm_chip *aml_chip = chip; + int id = index; + void __iomem *reg; + unsigned int mask = 0; + unsigned int val; + + if ((id < 0) && (id > 7)) { + dev_err(aml_chip->chip.dev, "index is not within the scope!\n"); + return -EINVAL; + } + + switch (id) { + case PWM_A: + reg = aml_chip->base + REG_BLINK_AB; + val = 1 << 8; + break; + case PWM_B: + reg = aml_chip->base + REG_BLINK_AB; + val = 1 << 9; + break; + case PWM_C: + reg = aml_chip->base + REG_BLINK_CD; + val = 1 << 8; + break; + case PWM_D: + reg = aml_chip->base + REG_BLINK_CD; + val = 1 << 9; + break; + case PWM_E: + reg = aml_chip->base + REG_BLINK_EF; + val = 1 << 8; + break; + case PWM_F: + reg = aml_chip->base + REG_BLINK_EF; + val = 1 << 9; + break; + case PWM_AO_A: + reg = aml_chip->ao_base + REG_BLINK_AO_AB; + val = 1 << 8; + break; + case PWM_AO_B: + reg = aml_chip->ao_base + REG_BLINK_AO_AB; + val = 1 << 9; + break; + default: + dev_err(aml_chip->chip.dev, + "blink,index is not legal\n"); + return -EINVAL; + + break; + } + pwm_set_reg_bits(reg, mask, val); + + return 0; +} +EXPORT_SYMBOL_GPL(pwm_blink_enable); + +/** + * pwm_blink_disable() - stop a constant PWM output toggling + * @chip: aml_pwm_chip struct + * @index: pwm channel to choose,like PWM_A or PWM_B + */ +int pwm_blink_disable(struct aml_pwm_chip *chip, int index) +{ + struct aml_pwm_chip *aml_chip = chip; + int id = index; + void __iomem *reg; + unsigned int mask; + unsigned int val; + + if ((id < 1) && (id > 7)) { + dev_err(aml_chip->chip.dev, "index is not within the scope!\n"); + return -EINVAL; + } + + switch (id) { + case PWM_A: + reg = aml_chip->base + REG_BLINK_AB; + mask = 1 << 8; + val = 0 << 8; + break; + case PWM_B: + reg = aml_chip->base + REG_BLINK_AB; + mask = 1 << 9; + val = 0 << 9; + break; + case PWM_C: + reg = aml_chip->base + REG_BLINK_CD; + mask = 1 << 8; + val = 0 << 8; + break; + case PWM_D: + reg = aml_chip->base + REG_BLINK_CD; + mask = 1 << 9; + val = 0 << 9; + break; + case PWM_E: + reg = aml_chip->base + REG_BLINK_EF; + mask = 1 << 8; + val = 0 << 8; + break; + case PWM_F: + reg = aml_chip->base + REG_BLINK_EF; + mask = 1 << 9; + val = 0 << 9; + break; + case PWM_AO_A: + reg = aml_chip->ao_base + REG_BLINK_AO_AB; + mask = 1 << 8; + val = 0 << 8; + break; + case PWM_AO_B: + reg = aml_chip->ao_base + REG_BLINK_AO_AB; + mask = 1 << 9; + val = 0 << 9; + break; + default: + dev_err(aml_chip->chip.dev, + "blink,index is not legal\n"); + return -EINVAL; + + break; + } + pwm_set_reg_bits(reg, mask, val); + + return 0; +} +EXPORT_SYMBOL_GPL(pwm_blink_disable); + +static ssize_t pwm_blink_enable_show(struct device *child, + struct device_attribute *attr, char *buf) +{ + struct aml_pwm_chip *chip = + (struct aml_pwm_chip *)dev_get_drvdata(child); + return sprintf(buf, "%d\n", chip->variant.blink_enable); +} + +static ssize_t pwm_blink_enable_store(struct device *child, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct aml_pwm_chip *chip = + (struct aml_pwm_chip *)dev_get_drvdata(child); + int val, ret, id, res; + + res = sscanf(buf, "%d %d", &val, &id); + if (res != 2) { + dev_err(child, + "blink enable,Can't parse pwm id,usage:[value index]\n"); + return -EINVAL; + } + if ((id < 1) && (id > 7)) { + dev_err(chip->chip.dev, "index is not within the scope!\n"); + return -EINVAL; + } + + switch (val) { + case 0: + ret = pwm_blink_disable(chip, id); + chip->variant.blink_enable = 0; + break; + case 1: + ret = pwm_blink_enable(chip, id); + chip->variant.blink_enable = 1; + break; + default: + ret = -EINVAL; + break; + } + + return ret ? : size; + +} + +/** + * pwm_set_blink_times() - set PWM blink times output toggling + * @chip: aml_pwm_chip struct + * @index: pwm channel to choose,like PWM_A or PWM_B + * @value: blink times to set,range from 1 to 15 + */ +int pwm_set_blink_times(struct aml_pwm_chip *chip, + int index, + int value) +{ + struct aml_pwm_chip *aml_chip = chip; + int id = index; + int val = value; + void __iomem *reg; + unsigned int clear_val; + unsigned int set_val; + + + if (((val <= 0) && (val > 15)) || ((id < 1) && (id > 7))) { + dev_err(aml_chip->chip.dev, + "value or index is not within the scope!\n"); + return -EINVAL; + } + + switch (id) { + case PWM_A: + reg = aml_chip->base + REG_BLINK_AB; + clear_val = 0xf; + set_val = val; + break; + case PWM_B: + reg = aml_chip->base + REG_BLINK_AB; + clear_val = 0xf << 4; + set_val = val << 4; + break; + case PWM_C: + reg = aml_chip->base + REG_BLINK_CD; + clear_val = 0xf; + set_val = val; + break; + case PWM_D: + reg = aml_chip->base + REG_BLINK_CD; + clear_val = 0xf << 4; + set_val = val << 4; + break; + case PWM_E: + reg = aml_chip->base + REG_BLINK_EF; + clear_val = 0xf; + set_val = val; + break; + case PWM_F: + reg = aml_chip->base + REG_BLINK_EF; + clear_val = 0xf << 4; + set_val = val << 4; + break; + case PWM_AO_A: + reg = aml_chip->ao_base + REG_BLINK_AO_AB; + clear_val = 0xf; + set_val = val; + break; + case PWM_AO_B: + reg = aml_chip->ao_base + REG_BLINK_AO_AB; + clear_val = 0xf << 4; + set_val = val << 4; + break; + default: + dev_err(aml_chip->chip.dev, + "bink times,index is not legal\n"); + return -EINVAL; + + break; + } + pwm_clear_reg_bits(reg, clear_val); + pwm_write_reg(reg, set_val); + + return 0; +} +EXPORT_SYMBOL_GPL(pwm_set_blink_times); + +static ssize_t pwm_blink_times_show(struct device *child, + struct device_attribute *attr, char *buf) +{ + struct aml_pwm_chip *chip = + (struct aml_pwm_chip *)dev_get_drvdata(child); + return sprintf(buf, "%d\n", chip->variant.blink_times); +} + +static ssize_t pwm_blink_times_store(struct device *child, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct aml_pwm_chip *chip = + (struct aml_pwm_chip *)dev_get_drvdata(child); + int val, ret, id, res; + + res = sscanf(buf, "%d %d", &val, &id); + if (res != 2) { + dev_err(child, + "Can't parse pwm id and value,usage:[value index]\n"); + return -EINVAL; + } + if (((val <= 0) && (val > 15)) || ((id < 0) && (id > 7))) { + dev_err(chip->chip.dev, + "value or index is not within the scope!\n"); + return -EINVAL; + } + + ret = pwm_set_blink_times(chip, id, val); + chip->variant.blink_times = val; + + return ret ? : size; +} + + +static DEVICE_ATTR(constant, 0644, + pwm_constant_show, + pwm_constant_store); +static DEVICE_ATTR(times, 0644, + pwm_times_show, + pwm_times_store); +static DEVICE_ATTR(blink_enable, 0644, + pwm_blink_enable_show, + pwm_blink_enable_store); +static DEVICE_ATTR(blink_times, 0644, + pwm_blink_times_show, + pwm_blink_times_store); + +static struct attribute *pwm_attrs[] = { + &dev_attr_constant.attr, + &dev_attr_times.attr, + &dev_attr_blink_enable.attr, + &dev_attr_blink_times.attr, + NULL, +}; + +static struct attribute_group pwm_attr_group = { + .attrs = pwm_attrs, +}; + +int meson_pwm_sysfs_init(struct device *dev) +{ + int retval; + + retval = sysfs_create_group(&dev->kobj, &pwm_attr_group); + return retval; +} diff --git a/include/dt-bindings/pwm/meson.h b/include/dt-bindings/pwm/meson.h new file mode 100644 index 000000000000..b5d42e6a0219 --- /dev/null +++ b/include/dt-bindings/pwm/meson.h @@ -0,0 +1,66 @@ +/* + * include/dt-bindings/pwm/meson.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef _DT_BINDINGS_PWM_MESON_H +#define _DT_BINDINGS_PWM_MESON_H + + +#define PWM_A 0 +#define PWM_B 1 +#define PWM_C 2 +#define PWM_D 3 +#define PWM_E 4 +#define PWM_F 5 +#define PWM_AO_A 6 +#define PWM_AO_B 7 + +/* + * Addtional 8 channels for txl + */ +#define PWM_A2 8 +#define PWM_B2 9 +#define PWM_C2 10 +#define PWM_D2 11 +#define PWM_E2 12 +#define PWM_F2 13 +#define PWM_AO_A2 14 +#define PWM_AO_B2 15 + +/* fclk_div3 + *--------------|\ + * fclk_div3 | \ + *--------------| \ get clock source + * vid_pll_clk | |--------------------- + *--------------| | + * XTAL | / + *--------------|/ + * vid_pll_clk is not defined and described now, + *waiting for CLKID_VID_PLL is suportted in the future, + * the macro is used for compiling passed. + */ +#define CLKID_VID_PLL + +/* + * 4 clock sources to choose + * keep the same order with pwm_aml_clk function in pwm driver + */ +#define XTAL_CLK 0 +#define VID_PLL_CLK 1 +#define FCLK_DIV4_CLK 2 +#define FCLK_DIV3_CLK 3 + +#endif diff --git a/include/linux/amlogic/pwm_meson.h b/include/linux/amlogic/pwm_meson.h new file mode 100644 index 000000000000..d5ab5e51727d --- /dev/null +++ b/include/linux/amlogic/pwm_meson.h @@ -0,0 +1,205 @@ +/* + * include/linux/amlogic/pwm_meson.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef _PWM_MESON_H +#define _PWM_MESON_H + +#include + + + +#define REG_PWM_A 0x0 +#define REG_PWM_B 0x4 +#define REG_MISC_AB 0x8 +#define REG_DS_A_B 0xc +#define REG_TIME_AB 0x10 +#define REG_PWM_A2 0x14 +#define REG_PWM_B2 0x18 +#define REG_BLINK_AB 0x1c + + +#define REG_PWM_C 0xf0 +#define REG_PWM_D 0xf4 +#define REG_MISC_CD 0xf8 +#define REG_DS_C_D 0xfc +#define REG_TIME_CD 0x100 +#define REG_PWM_C2 0x104 +#define REG_PWM_D2 0x108 +#define REG_BLINK_CD 0x10c + + + +#define REG_PWM_E 0x170 +#define REG_PWM_F 0x174 +#define REG_MISC_EF 0x178 +#define REG_DS_E_F 0x17c +#define REG_TIME_EF 0x180 +#define REG_PWM_E2 0x184 +#define REG_PWM_F2 0x188 +#define REG_BLINK_EF 0x18c + + +#define REG_PWM_AO_A 0x0 +#define REG_PWM_AO_B 0x4 +#define REG_MISC_AO_AB 0x8 +#define REG_DS_AO_A_B 0xc +#define REG_TIME_AO_AB 0x10 +#define REG_PWM_AO_A2 0x14 +#define REG_PWM_AO_B2 0x18 +#define REG_BLINK_AO_AB 0x1c + + +#define FIN_FREQ (24 * 1000) +#define DUTY_MAX 1024 + +#define AML_PWM_NUM 8 +#define AML_PWM_NUM_NEW 16 + + +enum pwm_channel { + PWM_A = 0, + PWM_B, + PWM_C, + PWM_D, + PWM_E, + PWM_F, + PWM_AO_A, + PWM_AO_B, + + PWM_A2, + PWM_B2, + PWM_C2, + PWM_D2, + PWM_E2, + PWM_F2, + PWM_AO_A2, + PWM_AO_B2, +}; + +/*pwm att*/ +struct aml_pwm_channel { + unsigned int pwm_hi; + unsigned int pwm_lo; + unsigned int pwm_pre_div; + + unsigned int period_ns; + unsigned int duty_ns; + unsigned int pwm_freq; +}; + +/*pwm regiset att*/ +struct aml_pwm_variant { + u8 output_mask; + u16 output_mask_new; +/* + *add for gxtvbb , gxl , gxm + */ + + unsigned int times; +/* + *include above and add for txl + */ + unsigned int constant; + unsigned int blink_enable; + unsigned int blink_times; +}; + +struct aml_pwm_chip { + struct pwm_chip chip; + void __iomem *base; + void __iomem *ao_base; + struct aml_pwm_variant variant; + u8 inverter_mask; + + unsigned int clk_mask; + struct clk *xtal_clk; + struct clk *vid_pll_clk; + struct clk *fclk_div4_clk; + struct clk *fclk_div3_clk; + +}; + +struct aml_pwm_chip *to_aml_pwm_chip(struct pwm_chip *chip); +void pwm_set_reg_bits(void __iomem *reg, + unsigned int mask, + const unsigned int val); + +void pwm_write_reg(void __iomem *reg, + const unsigned int val); + +void pwm_clear_reg_bits(void __iomem *reg, const unsigned int val); + +void pwm_write_reg1(void __iomem *reg, const unsigned int val); + + +int meson_pwm_sysfs_init(struct device *dev); + +#if IS_ENABLED(CONFIG_AMLOGIC_PWM) +int pwm_constant_enable(struct aml_pwm_chip *chip, int index); +int pwm_constant_disable(struct aml_pwm_chip *chip, int index); + +int pwm_blink_enable(struct aml_pwm_chip *chip, int index); +int pwm_blink_disable(struct aml_pwm_chip *chip, int index); + +int pwm_set_times(struct aml_pwm_chip *chip, + int index, int value); +int pwm_set_blink_times(struct aml_pwm_chip *chip, + int index, + int value); + +#else +static inline int pwm_constant_enable + (struct aml_pwm_chip *chip, int index) +{ + return -EINVAL; +} + +static inline int pwm_constant_disable + (struct aml_pwm_chip *chip, int index) +{ + return -EINVAL; +} + +static inline int pwm_blink_enable + (struct aml_pwm_chip *chip, int index) +{ + return -EINVAL; +} + +static inline int pwm_blink_disable + (struct aml_pwm_chip *chip, int index) +{ + return -EINVAL; +} + +static inline int pwm_set_times(struct aml_pwm_chip *chip, + int index, int value) +{ + return -EINVAL; +} + +static inline int pwm_set_blink_times(struct aml_pwm_chip *chip, + int index, + int value) +{ + return -EINVAL; +} + +#endif /* IS_ENABLED(CONFIG_PWM_MESON) */ + +#endif /* _PWM_MESON_H_ */ +