pwm: add pwm driver

PD#138714: initial add pwm driver

Change-Id: Ief5a3432642b8f2de8fecffaf51b5c055c696078
Signed-off-by: Jian Hu <jian.hu@amlogic.com>
Signed-off-by: Jianxin Pan <jianxin.pan@amlogic.com>
This commit is contained in:
Jian Hu
2017-01-03 16:01:24 +08:00
committed by Jianxin Pan
parent 8a3ce8ddbc
commit 76b338ae5f
15 changed files with 2111 additions and 4 deletions

View File

@@ -13503,3 +13503,10 @@ AMLOGIC CPU hotplug for dvfs and IPA
M: Jianxin Pan <jianxin.pan@amlogic.com>
F: drivers/amlogic/cpu_hotplug/*
AMLOGIC PWM DRIVER
M: Jian Hu <jian.hu@amlogic.com>
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

View File

@@ -17,7 +17,6 @@
/dts-v1/;
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include "mesongxl.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/gpio/gxl.h>
@@ -184,6 +183,36 @@
"usb1",
"usb1_to_ddr";
};
pwm {
compatible = "amlogic, meson-pwm";
status = "okay";
pwm-outputs = <PWM_A>,<PWM_B>,<PWM_C>,<PWM_D>,
<PWM_E>,<PWM_F>,<PWM_AO_A>,<PWM_AO_B>;
pwm-outputs-new = <PWM_A>,<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>;
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 = <XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,
<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>;
clock-select-new = <XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,
<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,
<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,
<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>;
/*all channels use the default clock source XTAL_CLK*/
/*and you can shoose it in file dt-bindings/pwm/meson.h*/
};
};
&efuse {

View File

@@ -18,8 +18,6 @@
/dts-v1/;
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/gpio/gxl.h>
#include "mesongxl.dtsi"
/ {
model = "Amlogic";
@@ -183,6 +181,36 @@
"usb1",
"usb1_to_ddr";
};
pwm {
compatible = "amlogic, meson-pwm";
status = "okay";
pwm-outputs = <PWM_A>,<PWM_B>,<PWM_C>,<PWM_D>,
<PWM_E>,<PWM_F>,<PWM_AO_A>,<PWM_AO_B>;
pwm-outputs-new = <PWM_A>,<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>;
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 = <XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,
<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>;
clock-select-new = <XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,
<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,
<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,
<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>;
/*all channels use the default clock source XTAL_CLK*/
/*and you can shoose it in file dt-bindings/pwm/meson.h*/
};
};
&efuse {

View File

@@ -17,7 +17,6 @@
/dts-v1/;
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include "mesongxm.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/gpio/gxl.h>
@@ -180,6 +179,35 @@
"usb1_to_ddr";
};
pwm {
compatible = "amlogic, meson-pwm";
status = "okay";
pwm-outputs = <PWM_A>,<PWM_B>,<PWM_C>,<PWM_D>,
<PWM_E>,<PWM_F>,<PWM_AO_A>,<PWM_AO_B>;
pwm-outputs-new = <PWM_A>,<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>;
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 = <XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,
<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>;
clock-select-new = <XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,
<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,
<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,
<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>,<XTAL_CLK>;
/*all channels use the default clock source XTAL_CLK*/
/*and you can shoose it in file dt-bindings/pwm/meson.h*/
};
};
&efuse {

View File

@@ -18,6 +18,8 @@
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/reset/amlogic,gxl-reset.h>
#include <dt-bindings/clock/amlogic,gxl-clkc.h>
#include <dt-bindings/gpio/gxl.h>
#include <dt-bindings/pwm/meson.h>
/ {
cpus:cpus {
#address-cells = <2>;

View File

@@ -18,6 +18,9 @@
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/reset/amlogic,gxl-reset.h>
#include <dt-bindings/clock/amlogic,gxl-clkc.h>
#include <dt-bindings/gpio/gxl.h>
#include <dt-bindings/pwm/meson.h>
/ {
cpus:cpus {
#address-cells = <2>;

View File

@@ -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

View File

@@ -47,5 +47,7 @@ source "drivers/amlogic/ethernet/phy/Kconfig"
source "drivers/amlogic/cpu_hotplug/Kconfig"
source "drivers/amlogic/pwm/Kconfig"
endmenu
endif

View File

@@ -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/

View File

@@ -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.

View File

@@ -0,0 +1,5 @@
#
# Makefile for PWM
#
obj-$(CONFIG_AMLOGIC_PWM) += pwm_meson.o pwm_meson_sysfs.o

File diff suppressed because it is too large Load Diff

View File

@@ -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 <linux/device.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/pwm.h>
#include <linux/amlogic/pwm_meson.h>
/**
* 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;
}

View File

@@ -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

View File

@@ -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 <linux/err.h>
#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_ */