From 22d7f4eb0eaf765c1d8e85604be02d2fe6ade7e7 Mon Sep 17 00:00:00 2001 From: ckkim Date: Tue, 30 Dec 2014 16:39:06 +0900 Subject: [PATCH] ODROIDC: Support hardware PWM(GPIOX_11, GPIOX_10) Change-Id: Ie258b5c5da3999af743e7e8fdbe22d5d9ed562af --- arch/arm/boot/dts/meson8b_odroidc.dts | 34 +- arch/arm/configs/odroidc_defconfig | 12 +- drivers/amlogic/Kconfig | 1 + drivers/amlogic/Makefile | 2 + drivers/amlogic/pwm/Kconfig | 17 + drivers/amlogic/pwm/Makefile | 2 + drivers/amlogic/pwm/pwm-ctrl.c | 429 ++++++++++++++++++++++++++ drivers/amlogic/pwm/pwm-meson.c | 334 ++++++++++++++++++++ 8 files changed, 827 insertions(+), 4 deletions(-) mode change 100755 => 100644 arch/arm/boot/dts/meson8b_odroidc.dts create mode 100644 drivers/amlogic/pwm/Kconfig create mode 100644 drivers/amlogic/pwm/Makefile create mode 100644 drivers/amlogic/pwm/pwm-ctrl.c create mode 100644 drivers/amlogic/pwm/pwm-meson.c diff --git a/arch/arm/boot/dts/meson8b_odroidc.dts b/arch/arm/boot/dts/meson8b_odroidc.dts old mode 100755 new mode 100644 index db133e324042..0230b34877ec --- a/arch/arm/boot/dts/meson8b_odroidc.dts +++ b/arch/arm/boot/dts/meson8b_odroidc.dts @@ -877,7 +877,23 @@ 9 0x00080000>; amlogic,pins = "GPIOX_8","GPIOX_9","GPIOX_10"; }; - }; + + odroid_pwm0:odroid_pwm0{ + amlogic,setmask=<2 0x00000008>; /*GPIOX_11 PWM_B Reg2[3]*/ + amlogic,clrmask=<3 0x00100000 + 7 0x40000000>; + amlogic,pins="GPIOX_11"; + }; + odroid_pwm1:odroid_pwm1{ + amlogic,setmask=<2 0x00000008 /*GPIOX_11 PWM_B Reg2[3]*/ + 9 0x00080000>; /*GPIOX_10 PWM_E Reg9[19]*/ + amlogic,clrmask=<3 0x00500100 + 4 0x00800000 + 6 0x00020000 + 7 0xc0000000>; + amlogic,pins="GPIOX_11","GPIOX_10"; + }; + }; meson-eth{ compatible = "amlogic,meson-eth"; dev_name = "meson-eth"; @@ -902,4 +918,20 @@ linux,default-trigger = "heartbeat"; }; }; + + pwm{ + compatible = "amlogic, odroid-pwm"; + dev_name = "meson_pwm"; + status = "ok"; + pinctrl-names = "odroid_pwm0","odroid_pwm1"; + pinctrl-0 = <&odroid_pwm0>; + pinctrl-1 = <&odroid_pwm1>; + }; + + pwm-ctrl { + compatible = "amlogic, pwm-ctrl"; + dev_name = "pwm-ctrl"; + status = "ok"; + }; + }; /* end of / */ diff --git a/arch/arm/configs/odroidc_defconfig b/arch/arm/configs/odroidc_defconfig index c4b5cc512663..4bf7d47aed47 100644 --- a/arch/arm/configs/odroidc_defconfig +++ b/arch/arm/configs/odroidc_defconfig @@ -1,6 +1,6 @@ # # Automatically generated file; DO NOT EDIT. -# Linux/arm 3.10.42 Kernel Configuration +# Linux/arm 3.10.43 Kernel Configuration # CONFIG_ARM=y CONFIG_SYS_SUPPORTS_APM_EMULATION=y @@ -1109,7 +1109,7 @@ CONFIG_PINCTRL_AMLOGIC=y # CONFIG_AMLOGIC_BOARD_HAS_PMU is not set # CONFIG_AML_PMU_ALGORITHM_SUPPORT is not set # CONFIG_AML_DVFS is not set -CONFIG_MESON_CS_DCDC_REGULATOR=y +# CONFIG_MESON_CS_DCDC_REGULATOR is not set # # Security key Support @@ -1141,6 +1141,8 @@ CONFIG_AML_WDT=y # CONFIG_AMLOGIC_SPICC_MASTER=m # CONFIG_AMLOGIC_SPICC_MASTER_DEBUG is not set +CONFIG_MESON_PWM=m +CONFIG_MESON_PWM_CTRL=m # # USB Support @@ -2003,6 +2005,7 @@ CONFIG_INPUT_MISC=y CONFIG_INPUT_UINPUT=m CONFIG_INPUT_GPIO=m # CONFIG_INPUT_PCF8574 is not set +# CONFIG_INPUT_PWM_BEEPER is not set # CONFIG_INPUT_GPIO_ROTARY_ENCODER is not set # CONFIG_INPUT_ADXL34X is not set # CONFIG_INPUT_IMS_PCU is not set @@ -2335,6 +2338,7 @@ CONFIG_HWMON=y # CONFIG_SENSORS_MAX6697 is not set # CONFIG_SENSORS_MCP3021 is not set # CONFIG_SENSORS_NCT6775 is not set +# CONFIG_SENSORS_NTC_THERMISTOR is not set # CONFIG_SENSORS_PC87360 is not set # CONFIG_SENSORS_PC87427 is not set # CONFIG_SENSORS_PCF8591 is not set @@ -2957,6 +2961,7 @@ CONFIG_LCD_CLASS_DEVICE=m # CONFIG_LCD_HX8357 is not set CONFIG_BACKLIGHT_CLASS_DEVICE=y CONFIG_BACKLIGHT_GENERIC=y +# CONFIG_BACKLIGHT_PWM is not set # CONFIG_BACKLIGHT_ADP8860 is not set # CONFIG_BACKLIGHT_ADP8870 is not set # CONFIG_BACKLIGHT_LM3630 is not set @@ -3397,6 +3402,7 @@ CONFIG_LEDS_GPIO=y # CONFIG_LEDS_PCA955X is not set # CONFIG_LEDS_PCA9633 is not set # CONFIG_LEDS_DAC124S085 is not set +# CONFIG_LEDS_PWM is not set # CONFIG_LEDS_REGULATOR is not set # CONFIG_LEDS_BD2802 is not set # CONFIG_LEDS_LT3593 is not set @@ -3595,7 +3601,7 @@ CONFIG_OF_IOMMU=y # CONFIG_EXTCON is not set # CONFIG_MEMORY is not set # CONFIG_IIO is not set -# CONFIG_PWM is not set +CONFIG_PWM=y CONFIG_IRQCHIP=y CONFIG_ARM_GIC=y # CONFIG_IPACK_BUS is not set diff --git a/drivers/amlogic/Kconfig b/drivers/amlogic/Kconfig index 87f5e90678fb..4df4087d23d8 100755 --- a/drivers/amlogic/Kconfig +++ b/drivers/amlogic/Kconfig @@ -26,6 +26,7 @@ source "drivers/amlogic/smartcard/Kconfig" source "drivers/amlogic/thermal/Kconfig" source "drivers/amlogic/watchdog/Kconfig" source "drivers/amlogic/spi/Kconfig" +source "drivers/amlogic/pwm/Kconfig" # # Block devices diff --git a/drivers/amlogic/Makefile b/drivers/amlogic/Makefile index a0227fbd2f17..09e5820ad7b5 100755 --- a/drivers/amlogic/Makefile +++ b/drivers/amlogic/Makefile @@ -129,3 +129,5 @@ obj-y += crypto/ #obj-$(CONFIG_MESON_TRUSTZONE) += trustzone/ obj-$(CONFIG_MESON_TRUSTZONE) += secure_monitor/ obj-y += spi/ + +obj-$(CONFIG_MESON_PWM) += pwm/ diff --git a/drivers/amlogic/pwm/Kconfig b/drivers/amlogic/pwm/Kconfig new file mode 100644 index 000000000000..4ef5f1c9aeac --- /dev/null +++ b/drivers/amlogic/pwm/Kconfig @@ -0,0 +1,17 @@ +config MESON_PWM + tristate "Amlogic PWM support" + depends on !MESON_CS_DCDC_REGULATOR + help + Generic PWM framework driver for AMLOGIC. + + To compile this driver as a module, choose M here: the module + will be called pwm-meson. + +config MESON_PWM_CTRL + tristate "Amlogic PWM Control driver" + depends on MESON_PWM + help + Generic PWM framework driver for Hardkernel. + + To compile this driver as a module, choose M here: the module + will be called pwm-meson. diff --git a/drivers/amlogic/pwm/Makefile b/drivers/amlogic/pwm/Makefile new file mode 100644 index 000000000000..51e4ccf9f726 --- /dev/null +++ b/drivers/amlogic/pwm/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_MESON_PWM) += pwm-meson.o +obj-$(CONFIG_MESON_PWM_CTRL) += pwm-ctrl.o diff --git a/drivers/amlogic/pwm/pwm-ctrl.c b/drivers/amlogic/pwm/pwm-ctrl.c new file mode 100644 index 000000000000..7c4ad5367e0c --- /dev/null +++ b/drivers/amlogic/pwm/pwm-ctrl.c @@ -0,0 +1,429 @@ +//[*]--------------------------------------------------------------------------------------------------[*] +// +// ODROID Board : PWM ctrl driver +// +//[*]--------------------------------------------------------------------------------------------------[*] +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +struct pwm_ctrl { + struct pwm_device *pwm0; + struct pwm_device *pwm1; + struct mutex mutex; + int pwm0_status,pwm1_status; + int freq0,freq1; + int duty0,duty1; +}; + +//[*]------------------------------------------------------------------------------------------------------------------ +// +// driver sysfs attribute define +// +//[*]------------------------------------------------------------------------------------------------------------------ +static ssize_t set_enable0 (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + unsigned int val; + + if(!(sscanf(buf, "%u\n", &val))) return -EINVAL; + dev_info(dev, "PWM_0 : %s [%d] \n",__FUNCTION__,val); + + mutex_lock(&ctrl->mutex); + if(val) { + ctrl->pwm0_status = 1; + pwm_disable(ctrl->pwm0); + pwm_config(ctrl->pwm0, ctrl->duty0, ctrl->freq0); + pwm_enable(ctrl->pwm0); + } + else { + pwm_disable(ctrl->pwm0); + pwm_config(ctrl->pwm0, 0, ctrl->freq0); + ctrl->pwm0_status = 0; + } + mutex_unlock(&ctrl->mutex); + + return count; +} + +static ssize_t show_status0 (struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + + if(ctrl->pwm0_status) return sprintf(buf, "PWM_0 : %s\n", "on"); + else return sprintf(buf, "PWM_0 : %s\n", "off"); +} + +//[*]------------------------------------------------------------------------------------------------------------------ +//[*]------------------------------------------------------------------------------------------------------------------ +static ssize_t set_duty0 (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + unsigned int val; + + if(!(sscanf(buf, "%u\n", &val))) return -EINVAL; + + if((val > 100)||(val < 0)){ + dev_err(dev, "PWM_0 : Invalid param. Duty cycle range is 0 to 100 \n"); + return count; + } + + dev_info(dev, "PWM_0 : %s [%d] \n",__FUNCTION__,val); + + mutex_lock(&ctrl->mutex); + ctrl->duty0 = val; + + if(ctrl->pwm0_status){ + pwm_disable(ctrl->pwm0); + pwm_config(ctrl->pwm0, ctrl->duty0, ctrl->freq0); + pwm_enable(ctrl->pwm0); + } + else { + pwm_disable(ctrl->pwm0); + pwm_config(ctrl->pwm0, 0, ctrl->freq0); + } + mutex_unlock(&ctrl->mutex); + + return count; +} + +static ssize_t show_duty0 (struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", ctrl->duty0); +} + +//[*]------------------------------------------------------------------------------------------------------------------ +//[*]------------------------------------------------------------------------------------------------------------------ +static ssize_t set_freq0 (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + unsigned int val; + + if(!(sscanf(buf, "%u\n", &val))) return -EINVAL; + + if((val < 0)){ + dev_err(dev, "PWM_0 : Invalid param. Duty cycle range is 0 to 100 \n"); + return count; + } + + dev_info(dev, "PWM_0 : %s [%d] \n",__FUNCTION__,val); + + mutex_lock(&ctrl->mutex); + ctrl->freq0 = val; + + if(ctrl->pwm0_status){ + pwm_disable(ctrl->pwm0); + pwm_config(ctrl->pwm0, ctrl->duty0, ctrl->freq0); + pwm_enable(ctrl->pwm0); + } + else { + pwm_disable(ctrl->pwm0); + pwm_config(ctrl->pwm0, 0, ctrl->freq0); + } + mutex_unlock(&ctrl->mutex); + + return count; +} + +static ssize_t show_freq0 (struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", ctrl->freq0); +} +//[*]--------------------------------------------------------------------------------------------------[*] +//[*]--------------------------------------------------------------------------------------------------[*] +static DEVICE_ATTR(enable0, S_IRWXUGO, show_status0, set_enable0); +static DEVICE_ATTR(freq0, S_IRWXUGO, show_freq0, set_freq0); +static DEVICE_ATTR(duty0, S_IRWXUGO, show_duty0, set_duty0); + +static struct attribute *pwm0_ctrl_sysfs_entries[] = { + &dev_attr_enable0.attr, + &dev_attr_freq0.attr, + &dev_attr_duty0.attr, + NULL +}; + +static struct attribute_group pwm0_ctrl_attr_group = { + .name = NULL, + .attrs = pwm0_ctrl_sysfs_entries, +}; + + +//[*]------------------------------------------------------------------------------------------------------------------ +static ssize_t set_enable1 (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + unsigned int val; + + if(!(sscanf(buf, "%u\n", &val))) return -EINVAL; + dev_info(dev, "PWM_1 : %s [%d] \n",__FUNCTION__,val); + + mutex_lock(&ctrl->mutex); + if(val) { + ctrl->pwm1_status = 1; + pwm_disable(ctrl->pwm1); + pwm_config(ctrl->pwm1, ctrl->duty1, ctrl->freq1); + pwm_enable(ctrl->pwm1); + } + else { + pwm_disable(ctrl->pwm1); + pwm_config(ctrl->pwm1, 0, ctrl->freq1); + ctrl->pwm1_status = 0; + } + mutex_unlock(&ctrl->mutex); + + return count; +} + +static ssize_t show_status1 (struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + + if(ctrl->pwm1_status) return sprintf(buf, "PWM_1 : %s\n", "on"); + else return sprintf(buf, "PWM_1 : %s\n", "off"); +} + +//[*]------------------------------------------------------------------------------------------------------------------ +//[*]------------------------------------------------------------------------------------------------------------------ +static ssize_t set_duty1 (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + unsigned int val; + + if(!(sscanf(buf, "%u\n", &val))) return -EINVAL; + + if((val > 100)||(val < 0)){ + dev_err(dev, "PWM_1 : Invalid param. Duty cycle range is 0 to 100 \n"); + return count; + } + dev_info(dev, "PWM_1 : %s [%d] \n",__FUNCTION__,val); + + mutex_lock(&ctrl->mutex); + ctrl->duty1 = val; + + if(ctrl->pwm1_status){ + pwm_disable(ctrl->pwm1); + pwm_config(ctrl->pwm1, ctrl->duty1, ctrl->freq1); + pwm_enable(ctrl->pwm1); + } + else { + pwm_disable(ctrl->pwm1); + pwm_config(ctrl->pwm1, 0, ctrl->freq1); + } + mutex_unlock(&ctrl->mutex); + + return count; +} + +static ssize_t show_duty1 (struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", ctrl->duty1); +} + +//[*]------------------------------------------------------------------------------------------------------------------ +//[*]------------------------------------------------------------------------------------------------------------------ +static ssize_t set_freq1 (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + unsigned int val; + + if(!(sscanf(buf, "%u\n", &val))) return -EINVAL; + + if((val < 10)||(val > 1000000)){ + dev_err(dev, "PWM_1 : Invalid param. Duty cycle range is 10 to 1MHz \n"); + return count; + } + dev_info(dev, "PWM_1 : %s [%d] \n",__FUNCTION__,val); + mutex_lock(&ctrl->mutex); + ctrl->freq1 = val; + + if(ctrl->pwm1_status){ + pwm_disable(ctrl->pwm1); + pwm_config(ctrl->pwm1, ctrl->duty1, ctrl->freq1); + pwm_enable(ctrl->pwm1); + } + else { + pwm_disable(ctrl->pwm1); + pwm_config(ctrl->pwm1, 0, ctrl->freq1); + } + mutex_unlock(&ctrl->mutex); + + return count; +} + +static ssize_t show_freq1 (struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", ctrl->freq1); +} +//[*]------------------------------------------------------------------------------------------------------------------ +//[*]------------------------------------------------------------------------------------------------------------------ +static DEVICE_ATTR(enable1, S_IRWXUGO, show_status1, set_enable1); +static DEVICE_ATTR(freq1, S_IRWXUGO, show_freq1, set_freq1); +static DEVICE_ATTR(duty1, S_IRWXUGO, show_duty1, set_duty1); +static struct attribute *pwm1_ctrl_sysfs_entries[] = { + &dev_attr_enable1.attr, + &dev_attr_freq1.attr, + &dev_attr_duty1.attr, + NULL +}; +static struct attribute_group pwm1_ctrl_attr_group = { + .name = NULL, + .attrs = pwm1_ctrl_sysfs_entries, +}; + +//[*]--------------------------------------------------------------------------------------------------[*] +//[*]--------------------------------------------------------------------------------------------------[*] +static int pwm_ctrl_resume(struct platform_device *dev) +{ + #if defined(DEBUG_PM_MSG) + dev_info(dev,"%s\n", __FUNCTION__); + #endif + + return 0; +} + +//[*]--------------------------------------------------------------------------------------------------[*] +static int pwm_ctrl_suspend(struct platform_device *dev, pm_message_t state) +{ + return 0; +} + +//[*]--------------------------------------------------------------------------------------------------[*] +static int pwm_ctrl_probe (struct platform_device *pdev) +{ + struct pwm_ctrl *ctrl; + struct device *dev = &pdev->dev; + int ret=0; + + ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) { + dev_err(&pdev->dev, "no memory for state\n"); + ret = -ENOMEM; + goto err_alloc; + } + + ctrl->pwm0 = pwm_request(0, "pwm-ctrl"); + if (IS_ERR(ctrl->pwm0)) { + dev_err(&pdev->dev, "unable to request legacy PWM\n"); + ret = PTR_ERR(ctrl->pwm0); + goto err_request; + } + + ctrl->pwm1 = pwm_request(1, "pwm-ctrl"); + if (IS_ERR(ctrl->pwm1)) { + dev_err(&pdev->dev, "unable to request legacy PWM\n"); + ctrl->pwm1=NULL; + } + + mutex_init(&ctrl->mutex); + dev_set_drvdata(dev, ctrl); + + ret =sysfs_create_group(&dev->kobj, &pwm0_ctrl_attr_group); + if(ret < 0) { + dev_err(&pdev->dev, "failed to create sysfs group !!\n"); + } + + if(ctrl->pwm1 != NULL){ + ret =sysfs_create_group(&dev->kobj, &pwm1_ctrl_attr_group); + if(ret < 0) { + dev_err(&pdev->dev, "failed to create sysfs group !!\n"); + } + } + + return 0; + +err_request: + devm_kfree(&pdev->dev, ctrl); + kfree(ctrl); +err_alloc: + return ret; +} + +//[*]--------------------------------------------------------------------------------------------------[*] +static int pwm_ctrl_remove (struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwm_ctrl *ctrl = dev_get_drvdata(dev); + + + if(ctrl->pwm1 != NULL){ + sysfs_remove_group(&dev->kobj, &pwm1_ctrl_attr_group); + } + sysfs_remove_group(&dev->kobj, &pwm0_ctrl_attr_group); + + if(ctrl->pwm1) + pwm_free(ctrl->pwm1); + + if(ctrl->pwm0) + pwm_free(ctrl->pwm0); + + devm_kfree(dev, ctrl); + + return 0; +} + +//[*]--------------------------------------------------------------------------------------------------[*] +#if defined(CONFIG_OF) +static const struct of_device_id pwm_ctrl_dt[] = { + { .compatible = "amlogic, pwm-ctrl" }, + { }, +}; +#endif + +//[*]--------------------------------------------------------------------------------------------------[*] +static struct platform_driver pwm_ctrl_driver = { + .driver = { + .name = "pwm-ctrl", + .owner = THIS_MODULE, +#if defined(CONFIG_OF) + .of_match_table = of_match_ptr(pwm_ctrl_dt), +#endif + }, + .probe = pwm_ctrl_probe, + .remove = pwm_ctrl_remove, + .suspend = pwm_ctrl_suspend, + .resume = pwm_ctrl_resume, +}; + +//[*]--------------------------------------------------------------------------------------------------[*] +static int __init pwm_ctrl_init(void) +{ + return platform_driver_register(&pwm_ctrl_driver); +} + +//[*]--------------------------------------------------------------------------------------------------[*] +static void __exit pwm_ctrl_exit(void) +{ + platform_driver_unregister(&pwm_ctrl_driver); +} + +//[*]--------------------------------------------------------------------------------------------------[*] +module_init(pwm_ctrl_init); +module_exit(pwm_ctrl_exit); + +//[*]--------------------------------------------------------------------------------------------------[*] +MODULE_DESCRIPTION("PWM ctrl driver for odroid-Dev board"); +MODULE_AUTHOR("HardKernel"); +MODULE_LICENSE("GPL"); + +//[*]--------------------------------------------------------------------------------------------------[*] +//[*]--------------------------------------------------------------------------------------------------[*] diff --git a/drivers/amlogic/pwm/pwm-meson.c b/drivers/amlogic/pwm/pwm-meson.c new file mode 100644 index 000000000000..6838fc9a0206 --- /dev/null +++ b/drivers/amlogic/pwm/pwm-meson.c @@ -0,0 +1,334 @@ +/* + * pwm-meson.c + * + * Support for Meson current source PWM + * + * Copyright (C) 2012 Elvis Yu + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, + * whether express or implied; 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int npwm=1; +module_param(npwm,int,0644); +MODULE_PARM_DESC(npwm,"\n odroid-c1 The number of available pwm (max 2-port)\n"); + +#define PWM_B 0 +#define PWM_E 1 +#define FIN_FREQ (24 * 1000) + +struct meson_pwm_device { + unsigned int freq; + unsigned int duty; + unsigned char pwm_id; + struct pwm_device *pwm; +}; + +struct meson_chip { + struct platform_device *pdev; + struct pwm_chip chip; + struct meson_pwm_device *meson_pwm[2]; + struct pinctrl *pinctrl; +}; + +#define to_meson_chip(chip) container_of(chip, struct meson_chip, chip) +#define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg) + +static void meson_pwm_init(struct device *dev, int pwmn) +{ + unsigned long flags; + if(pwmn == 1) { + local_irq_save(flags); + aml_write_reg32(P_PWM_MISC_REG_AB, (aml_read_reg32(P_PWM_MISC_REG_AB) & ~(0x7f << 16)) | ((1 << 23))); + local_irq_restore(flags); + } + else { + local_irq_save(flags); + aml_write_reg32(P_PWM_MISC_REG_AB, (aml_read_reg32(P_PWM_MISC_REG_AB) & ~(0x7f << 16)) | ((1 << 23))); + aml_write_reg32(P_PWM_MISC_REG_EF, (aml_read_reg32(P_PWM_MISC_REG_EF) & ~(0x7f << 8)) | ((1 << 15))); + local_irq_restore(flags); + } + return; +} + +static int meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct meson_chip *meson = to_meson_chip(chip); + struct meson_pwm_device *meson_pwm; + unsigned int id = pwm->pwm; + unsigned long flags; + + meson_pwm = meson->meson_pwm[id]; + local_irq_save(flags); + switch (id) { + case PWM_B: + aml_set_reg32_bits(P_PWM_MISC_REG_AB, 1, 1, 1); //enable pwm_b + break; + case PWM_E: + aml_set_reg32_bits(P_PWM_MISC_REG_EF, 1, 0, 1); //enable pwm_e + break; + } + local_irq_restore(flags); + + return 0; +} + +static void meson_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct meson_chip *meson = to_meson_chip(chip); + struct meson_pwm_device *meson_pwm; + unsigned int id = pwm->pwm; + unsigned long flags; + + meson_pwm = meson->meson_pwm[id]; + local_irq_save(flags); + switch (id) { + case PWM_B: + aml_set_reg32_bits(P_PWM_MISC_REG_AB, 0, 1, 1); //disable pwm_b + break; + case PWM_E: + aml_set_reg32_bits(P_PWM_MISC_REG_EF, 0, 0, 1); //disable pwm_e + break; + } + local_irq_restore(flags); + return; +} + +static int meson_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_percent, int pwm_freq) +{ + struct meson_chip *meson = to_meson_chip(chip); + struct meson_pwm_device *meson_pwm; + unsigned int id = pwm->pwm; + struct device *dev = chip->dev; + unsigned pwm_hi = 0, pwm_lo = 0; + unsigned fout_freq=0, pwm_cnt, pwm_pre_div; + unsigned long flags=0; + int i=0; + + if((duty_percent<0)||(duty_percent>100)){ + dev_err(dev, "Not available duty_percent... error!!!\n"); + return -EINVAL; + } + meson_pwm = meson->meson_pwm[id]; + + fout_freq = ((pwm_freq >= (FIN_FREQ * 500)) ? (FIN_FREQ * 500) : pwm_freq); + for (i=0; i<0x7f; i++) { + pwm_pre_div = i; + pwm_cnt = FIN_FREQ * 1000 / (pwm_freq * (pwm_pre_div + 1)) - 2; + if (pwm_cnt <= 0xffff) + break; + } + if(duty_percent==0) { + pwm_hi=0; + pwm_lo=pwm_cnt; + goto div_set; + } + if(duty_percent==100) { + pwm_hi=pwm_cnt; + pwm_lo=0; + goto div_set; + } + pwm_hi = (pwm_cnt*duty_percent)/100; + pwm_lo = (pwm_cnt*(100-duty_percent))/100; + +div_set : + local_irq_save(flags); + switch(id){ + case PWM_B: + aml_set_reg32_bits(P_PWM_MISC_REG_AB, pwm_pre_div, 16, 7); //pwm_b_clk_div + aml_write_reg32(P_PWM_PWM_B, (pwm_hi << 16) | (pwm_lo)); + break; + case PWM_E: + aml_set_reg32_bits(P_PWM_MISC_REG_EF, pwm_pre_div, 8, 7); //pwm_e_clk_div + aml_write_reg32(P_PWM_PWM_E, (pwm_hi << 16) | (pwm_lo)); + break; + default: + break; + } + local_irq_restore(flags); + + return 0; +} + +static int meson_pwm_request(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct meson_chip *meson = to_meson_chip(chip); + unsigned int id = pwm->pwm; + + meson->meson_pwm[id] = devm_kzalloc(chip->dev, sizeof(struct meson_pwm_device), GFP_KERNEL); + if (!meson->meson_pwm[id]) + return -ENOMEM; + + meson->meson_pwm[id]->pwm_id = id; + + meson->meson_pwm[id]->pwm = pwm; + pwm_set_chip_data(pwm, meson->meson_pwm[id]); + + return 0; +} + +static void meson_pwm_free(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct meson_chip *meson = to_meson_chip(chip); + unsigned int id = pwm->pwm; + + if(meson->pinctrl){ + devm_pinctrl_put(meson->pinctrl); + meson->pinctrl=NULL; + } + devm_kfree(chip->dev, meson->meson_pwm[id]); +} + +static struct pwm_ops meson_pwm_ops = { + .request = meson_pwm_request, + .free = meson_pwm_free, + .enable = meson_pwm_enable, + .disable = meson_pwm_disable, + .config = meson_pwm_config, + .owner = THIS_MODULE, +}; + +static int meson_pwm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct meson_chip *meson; + char prop_name[20]; + int ret=0; + + if (!np) { + return -ENODEV; + } + + if((npwm<=0)||(npwm>2)){ + dev_err(dev, "Available pwm_device number error.\n"); + return -EINVAL; + } + + meson = devm_kzalloc(&pdev->dev, sizeof(*meson), GFP_KERNEL); + if (meson == NULL) { + dev_err(dev, "failed to allocate pwm_device\n"); + return -ENOMEM; + } + + meson->pdev = pdev; + meson->chip.dev = &pdev->dev; + meson->chip.ops = &meson_pwm_ops; + meson->chip.base = -1; + meson->chip.npwm = npwm; + + ret = pwmchip_add(&meson->chip); + if (ret < 0) { + dev_err(dev, "failed to register pwm\n"); + return ret; + } + platform_set_drvdata(pdev, meson); + + if(npwm == 2) strcpy(prop_name,"odroid_pwm1"); + else strcpy(prop_name,"odroid_pwm0"); + + meson->pinctrl = devm_pinctrl_get_select(&pdev->dev, prop_name); + if(IS_ERR(meson->pinctrl)) { + dev_err(&pdev->dev, "pinmux error\n"); + return -ENODEV; + } + dev_info(&pdev->dev, "pinctrl_name = %s\n", prop_name); + + meson_pwm_init(dev, npwm); + + dev_info(dev, "register pwm device.. %s\n",__func__); + return 0; +} + +static int meson_pwm_remove(struct platform_device *pdev) +{ + struct meson_chip *meson = platform_get_drvdata(pdev); + int err; + + + err = pwmchip_remove(&meson->chip); + if (err < 0) + return err; + + devm_kfree(meson->chip.dev,meson); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int meson_pwm_suspend(struct device *dev) +{ + return 0; +} + +static int meson_pwm_resume(struct device *dev) +{ + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(meson_pwm_pm_ops, meson_pwm_suspend, + meson_pwm_resume); + +#ifdef CONFIG_OF +static const struct of_device_id meson_pwm_of_match[]={ + { .compatible = "amlogic, odroid-pwm", }, + {}, +}; +#else +#define meson_pwm_of_match NULL +#endif + +static struct platform_driver meson_pwm_driver = { + .driver = { + .name = "meson_pwm", + .owner = THIS_MODULE, + .of_match_table = meson_pwm_of_match, + .pm = &meson_pwm_pm_ops, + }, + .probe = meson_pwm_probe, + .remove = meson_pwm_remove, +}; + +static int __init module_pwm_init(void) +{ + int ret; + + ret = platform_driver_register(&meson_pwm_driver); + if (ret) + pr_err("failed to add pwm driver\n"); + + return ret; +} +static void __exit module_pwm_exit(void) +{ + platform_driver_unregister(&meson_pwm_driver); +} +module_init(module_pwm_init); +module_exit(module_pwm_exit); + +MODULE_DESCRIPTION("AMLogic meson8b PWM driver"); +MODULE_AUTHOR("HardKernel"); +MODULE_LICENSE("GPL"); \ No newline at end of file