forked from luck/tmp_suning_uos_patched
pwm: Changes for v4.17-rc1
This set of changes adds support for more generations of the RCar controller as well as runtime PM support. The JZ4740 driver gains support for device tree and can now be used on all Ingenic SoCs. Rounding things off is a random assortment of fixes and cleanups all across the board. -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEEiOrDCAFJzPfAjcif3SOs138+s6EFAlrPJeAZHHRoaWVycnku cmVkaW5nQGdtYWlsLmNvbQAKCRDdI6zXfz6zoUQvEACaAkXPs3rvvHVF7pDo7SMV 3WMJu1oOiZ7Gy0VAooRyhwI/rNBeF4PYuHuxa2+NeuWX8MBN41dRWAp8Am4kMnOE Lxp259qQTNGdGQTnSXP/sbYTKzAqF/3G0XtkhGejS3AzYSnhk6sIGcCevS4MD8bV I/jSigPO4ByizyEJsnAkZPUw5js2CsE2xSnGKRsITZBK7yDi/cwB2cOLm0+psCKu qYTlOHTc3myRKYESw5rmzB+cRyLGMkaq9SQzLZ8E2PP7zIY1QbrmzufQ1ovbpffl 065dS8+m1ecRMXUfFCLnHZPk/S5W6xZMskVJ0GIKC3xBPefbpCkkcA9A/gXkwH4y FJ4lCROnB8gE3ITGkqzmZNBt3PKySIubEv1mCzdHVx3j4IEmF/M1Xc4wxz9qD+tD ay+h1Vv1RbQlYlMqcOkxBpZd6sUnDIlIvIJ35IYUvD80Ap24DIU05NR1xsq6DMaD c0N13an4gyK1t2O0YRcdmhHhIgwwaGhka5+8+r2hCqSVf/uzgyn26vzyqttrHuVc CNYeMObuotr72LVHzXHkr4I/h+c7KE1B8RYKLZvUQuIdOvSb7Z9n32n1dBRILDH8 ppb+Y4azLDvAF4SAt5lZhYsMmB8vuCp4TA5LKwBEsylwTwOL9ghL7luYAOF4UroH qigluQk3Sf5GF1jrp9hTew== =JiRM -----END PGP SIGNATURE----- Merge tag 'pwm/for-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm Pull pwm updates from Thierry Reding: "This set of changes adds support for more generations of the RCar controller as well as runtime PM support. The JZ4740 driver gains support for device tree and can now be used on all Ingenic SoCs. Rounding things off is a random assortment of fixes and cleanups all across the board" * tag 'pwm/for-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (29 commits) pwm: rcar: Add suspend/resume support pwm: rcar: Use PM Runtime to control module clock dt-bindings: pwm: rcar: Add bindings for R-Car M3N support pwm: rcar: Fix a condition to prevent mismatch value setting to duty pwm: sysfs: Use put_device() instead of kfree() dt-bindings: pwm: sunxi: Add new compatible strings pwm: sun4i: Simplify controller mapping pwm: sun4i: Drop unused .has_rdy member pwm: sun4i: Properly check current state pwm: Remove depends on AVR32 pwm: stm32: LPTimer: Use 3 cells ->of_xlate() dt-bindings: pwm-stm32-lp: Add #pwm-cells pwm: stm32: Protect common prescaler for all channels pwm: stm32: Remove unused struct device pwm: mediatek: Improve precision in rate calculation pwm: mediatek: Remove redundant MODULE_ALIAS entries pwm: mediatek: Fix up PWM4 and PWM5 malfunction on MT7623 pwm: jz4740: Enable for all Ingenic SoCs pwm: jz4740: Add support for devicetree pwm: jz4740: Implement ->set_polarity() ...
This commit is contained in:
commit
daf3ef6e96
25
Documentation/devicetree/bindings/pwm/ingenic,jz47xx-pwm.txt
Normal file
25
Documentation/devicetree/bindings/pwm/ingenic,jz47xx-pwm.txt
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
Ingenic JZ47xx PWM Controller
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: One of:
|
||||||
|
* "ingenic,jz4740-pwm"
|
||||||
|
* "ingenic,jz4770-pwm"
|
||||||
|
* "ingenic,jz4780-pwm"
|
||||||
|
- #pwm-cells: Should be 3. See pwm.txt in this directory for a description
|
||||||
|
of the cells format.
|
||||||
|
- clocks : phandle to the external clock.
|
||||||
|
- clock-names : Should be "ext".
|
||||||
|
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
pwm: pwm@10002000 {
|
||||||
|
compatible = "ingenic,jz4740-pwm";
|
||||||
|
reg = <0x10002000 0x1000>;
|
||||||
|
|
||||||
|
#pwm-cells = <3>;
|
||||||
|
|
||||||
|
clocks = <&ext>;
|
||||||
|
clock-names = "ext";
|
||||||
|
};
|
|
@ -7,6 +7,8 @@ See ../mfd/stm32-lptimer.txt for details about the parent node.
|
||||||
|
|
||||||
Required parameters:
|
Required parameters:
|
||||||
- compatible: Must be "st,stm32-pwm-lp".
|
- compatible: Must be "st,stm32-pwm-lp".
|
||||||
|
- #pwm-cells: Should be set to 3. This PWM chip uses the default 3 cells
|
||||||
|
bindings defined in pwm.txt.
|
||||||
|
|
||||||
Optional properties:
|
Optional properties:
|
||||||
- pinctrl-names: Set to "default".
|
- pinctrl-names: Set to "default".
|
||||||
|
@ -18,6 +20,7 @@ Example:
|
||||||
...
|
...
|
||||||
pwm {
|
pwm {
|
||||||
compatible = "st,stm32-pwm-lp";
|
compatible = "st,stm32-pwm-lp";
|
||||||
|
#pwm-cells = <3>;
|
||||||
pinctrl-names = "default";
|
pinctrl-names = "default";
|
||||||
pinctrl-0 = <&lppwm1_pins>;
|
pinctrl-0 = <&lppwm1_pins>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,8 @@ Required properties:
|
||||||
- "allwinner,sun5i-a13-pwm"
|
- "allwinner,sun5i-a13-pwm"
|
||||||
- "allwinner,sun7i-a20-pwm"
|
- "allwinner,sun7i-a20-pwm"
|
||||||
- "allwinner,sun8i-h3-pwm"
|
- "allwinner,sun8i-h3-pwm"
|
||||||
|
- "allwinner,sun50i-a64-pwm", "allwinner,sun5i-a13-pwm"
|
||||||
|
- "allwinner,sun50i-h5-pwm", "allwinner,sun5i-a13-pwm"
|
||||||
- reg: physical base address and length of the controller's registers
|
- reg: physical base address and length of the controller's registers
|
||||||
- #pwm-cells: should be 3. See pwm.txt in this directory for a description of
|
- #pwm-cells: should be 3. See pwm.txt in this directory for a description of
|
||||||
the cells format.
|
the cells format.
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
Required Properties:
|
Required Properties:
|
||||||
- compatible: should be "renesas,pwm-rcar" and one of the following.
|
- compatible: should be "renesas,pwm-rcar" and one of the following.
|
||||||
|
- "renesas,pwm-r8a7743": for RZ/G1M
|
||||||
|
- "renesas,pwm-r8a7745": for RZ/G1E
|
||||||
- "renesas,pwm-r8a7778": for R-Car M1A
|
- "renesas,pwm-r8a7778": for R-Car M1A
|
||||||
- "renesas,pwm-r8a7779": for R-Car H1
|
- "renesas,pwm-r8a7779": for R-Car H1
|
||||||
- "renesas,pwm-r8a7790": for R-Car H2
|
- "renesas,pwm-r8a7790": for R-Car H2
|
||||||
|
@ -9,6 +11,7 @@ Required Properties:
|
||||||
- "renesas,pwm-r8a7794": for R-Car E2
|
- "renesas,pwm-r8a7794": for R-Car E2
|
||||||
- "renesas,pwm-r8a7795": for R-Car H3
|
- "renesas,pwm-r8a7795": for R-Car H3
|
||||||
- "renesas,pwm-r8a7796": for R-Car M3-W
|
- "renesas,pwm-r8a7796": for R-Car M3-W
|
||||||
|
- "renesas,pwm-r8a77965": for R-Car M3-N
|
||||||
- "renesas,pwm-r8a77995": for R-Car D3
|
- "renesas,pwm-r8a77995": for R-Car D3
|
||||||
- reg: base address and length of the registers block for the PWM.
|
- reg: base address and length of the registers block for the PWM.
|
||||||
- #pwm-cells: should be 2. See pwm.txt in this directory for a description of
|
- #pwm-cells: should be 2. See pwm.txt in this directory for a description of
|
||||||
|
@ -17,13 +20,15 @@ Required Properties:
|
||||||
- pinctrl-0: phandle, referring to a default pin configuration node.
|
- pinctrl-0: phandle, referring to a default pin configuration node.
|
||||||
- pinctrl-names: Set to "default".
|
- pinctrl-names: Set to "default".
|
||||||
|
|
||||||
Example: R8A7790 (R-Car H2) PWM Timer node
|
Example: R8A7743 (RZ/G1M) PWM Timer node
|
||||||
|
|
||||||
pwm0: pwm@e6e30000 {
|
pwm0: pwm@e6e30000 {
|
||||||
compatible = "renesas,pwm-r8a7790", "renesas,pwm-rcar";
|
compatible = "renesas,pwm-r8a7743", "renesas,pwm-rcar";
|
||||||
reg = <0 0xe6e30000 0 0x8>;
|
reg = <0 0xe6e30000 0 0x8>;
|
||||||
|
clocks = <&cpg CPG_MOD 523>;
|
||||||
|
power-domains = <&sysc R8A7743_PD_ALWAYS_ON>;
|
||||||
|
resets = <&cpg 523>;
|
||||||
#pwm-cells = <2>;
|
#pwm-cells = <2>;
|
||||||
clocks = <&mstp5_clks R8A7790_CLK_PWM>;
|
|
||||||
pinctrl-0 = <&pwm0_pins>;
|
pinctrl-0 = <&pwm0_pins>;
|
||||||
pinctrl-names = "default";
|
pinctrl-names = "default";
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
Required Properties:
|
Required Properties:
|
||||||
|
|
||||||
- compatible: should be one of the following.
|
- compatible: should be one of the following.
|
||||||
- "renesas,tpu-r8a73a4": for R8A77A4 (R-Mobile APE6) compatible PWM controller.
|
- "renesas,tpu-r8a73a4": for R8A73A4 (R-Mobile APE6) compatible PWM controller.
|
||||||
- "renesas,tpu-r8a7740": for R8A7740 (R-Mobile A1) compatible PWM controller.
|
- "renesas,tpu-r8a7740": for R8A7740 (R-Mobile A1) compatible PWM controller.
|
||||||
|
- "renesas,tpu-r8a7743": for R8A7743 (RZ/G1M) compatible PWM controller.
|
||||||
|
- "renesas,tpu-r8a7745": for R8A7745 (RZ/G1E) compatible PWM controller.
|
||||||
- "renesas,tpu-r8a7790": for R8A7790 (R-Car H2) compatible PWM controller.
|
- "renesas,tpu-r8a7790": for R8A7790 (R-Car H2) compatible PWM controller.
|
||||||
- "renesas,tpu": for generic R-Car TPU PWM controller.
|
- "renesas,tpu": for generic R-Car and RZ/G1 TPU PWM controller.
|
||||||
|
|
||||||
- reg: Base address and length of each memory resource used by the PWM
|
- reg: Base address and length of each memory resource used by the PWM
|
||||||
controller hardware module.
|
controller hardware module.
|
||||||
|
@ -18,10 +20,10 @@ Required Properties:
|
||||||
Please refer to pwm.txt in this directory for details of the common PWM bindings
|
Please refer to pwm.txt in this directory for details of the common PWM bindings
|
||||||
used by client devices.
|
used by client devices.
|
||||||
|
|
||||||
Example: R8A7740 (R-Car A1) TPU controller node
|
Example: R8A7740 (R-Mobile A1) TPU controller node
|
||||||
|
|
||||||
tpu: pwm@e6600000 {
|
tpu: pwm@e6600000 {
|
||||||
compatible = "renesas,tpu-r8a7740", "renesas,tpu";
|
compatible = "renesas,tpu-r8a7740", "renesas,tpu";
|
||||||
reg = <0xe6600000 0x100>;
|
reg = <0xe6600000 0x148>;
|
||||||
#pwm-cells = <3>;
|
#pwm-cells = <3>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -200,10 +200,10 @@ config PWM_IMX
|
||||||
will be called pwm-imx.
|
will be called pwm-imx.
|
||||||
|
|
||||||
config PWM_JZ4740
|
config PWM_JZ4740
|
||||||
tristate "Ingenic JZ4740 PWM support"
|
tristate "Ingenic JZ47xx PWM support"
|
||||||
depends on MACH_JZ4740
|
depends on MACH_INGENIC
|
||||||
help
|
help
|
||||||
Generic PWM framework driver for Ingenic JZ4740 based
|
Generic PWM framework driver for Ingenic JZ47xx based
|
||||||
machines.
|
machines.
|
||||||
|
|
||||||
To compile this driver as a module, choose M here: the module
|
To compile this driver as a module, choose M here: the module
|
||||||
|
|
|
@ -401,7 +401,6 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev)
|
||||||
tcbpwm = devm_kzalloc(&pdev->dev, sizeof(*tcbpwm), GFP_KERNEL);
|
tcbpwm = devm_kzalloc(&pdev->dev, sizeof(*tcbpwm), GFP_KERNEL);
|
||||||
if (tcbpwm == NULL) {
|
if (tcbpwm == NULL) {
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
dev_err(&pdev->dev, "failed to allocate memory\n");
|
|
||||||
goto err_free_tc;
|
goto err_free_tc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#define MX3_PWMSAR 0x0C /* PWM Sample Register */
|
#define MX3_PWMSAR 0x0C /* PWM Sample Register */
|
||||||
#define MX3_PWMPR 0x10 /* PWM Period Register */
|
#define MX3_PWMPR 0x10 /* PWM Period Register */
|
||||||
#define MX3_PWMCR_PRESCALER(x) ((((x) - 1) & 0xFFF) << 4)
|
#define MX3_PWMCR_PRESCALER(x) ((((x) - 1) & 0xFFF) << 4)
|
||||||
|
#define MX3_PWMCR_STOPEN (1 << 25)
|
||||||
#define MX3_PWMCR_DOZEEN (1 << 24)
|
#define MX3_PWMCR_DOZEEN (1 << 24)
|
||||||
#define MX3_PWMCR_WAITEN (1 << 23)
|
#define MX3_PWMCR_WAITEN (1 << 23)
|
||||||
#define MX3_PWMCR_DBGEN (1 << 22)
|
#define MX3_PWMCR_DBGEN (1 << 22)
|
||||||
|
@ -210,7 +211,7 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
writel(period_cycles, imx->mmio_base + MX3_PWMPR);
|
writel(period_cycles, imx->mmio_base + MX3_PWMPR);
|
||||||
|
|
||||||
cr = MX3_PWMCR_PRESCALER(prescale) |
|
cr = MX3_PWMCR_PRESCALER(prescale) |
|
||||||
MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |
|
MX3_PWMCR_STOPEN | MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |
|
||||||
MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH |
|
MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH |
|
||||||
MX3_PWMCR_EN;
|
MX3_PWMCR_EN;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <linux/gpio.h>
|
#include <linux/gpio.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/pwm.h>
|
#include <linux/pwm.h>
|
||||||
|
|
||||||
|
@ -71,9 +72,15 @@ static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
{
|
{
|
||||||
uint32_t ctrl = jz4740_timer_get_ctrl(pwm->hwpwm);
|
uint32_t ctrl = jz4740_timer_get_ctrl(pwm->hwpwm);
|
||||||
|
|
||||||
|
/* Disable PWM output.
|
||||||
|
* In TCU2 mode (channel 1/2 on JZ4750+), this must be done before the
|
||||||
|
* counter is stopped, while in TCU1 mode the order does not matter.
|
||||||
|
*/
|
||||||
ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE;
|
ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE;
|
||||||
jz4740_timer_disable(pwm->hwpwm);
|
|
||||||
jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
|
jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
|
||||||
|
|
||||||
|
/* Stop counter */
|
||||||
|
jz4740_timer_disable(pwm->hwpwm);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
|
@ -124,10 +131,29 @@ static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int jz4740_pwm_set_polarity(struct pwm_chip *chip,
|
||||||
|
struct pwm_device *pwm, enum pwm_polarity polarity)
|
||||||
|
{
|
||||||
|
uint32_t ctrl = jz4740_timer_get_ctrl(pwm->pwm);
|
||||||
|
|
||||||
|
switch (polarity) {
|
||||||
|
case PWM_POLARITY_NORMAL:
|
||||||
|
ctrl &= ~JZ_TIMER_CTRL_PWM_ACTIVE_LOW;
|
||||||
|
break;
|
||||||
|
case PWM_POLARITY_INVERSED:
|
||||||
|
ctrl |= JZ_TIMER_CTRL_PWM_ACTIVE_LOW;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct pwm_ops jz4740_pwm_ops = {
|
static const struct pwm_ops jz4740_pwm_ops = {
|
||||||
.request = jz4740_pwm_request,
|
.request = jz4740_pwm_request,
|
||||||
.free = jz4740_pwm_free,
|
.free = jz4740_pwm_free,
|
||||||
.config = jz4740_pwm_config,
|
.config = jz4740_pwm_config,
|
||||||
|
.set_polarity = jz4740_pwm_set_polarity,
|
||||||
.enable = jz4740_pwm_enable,
|
.enable = jz4740_pwm_enable,
|
||||||
.disable = jz4740_pwm_disable,
|
.disable = jz4740_pwm_disable,
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
|
@ -149,6 +175,8 @@ static int jz4740_pwm_probe(struct platform_device *pdev)
|
||||||
jz4740->chip.ops = &jz4740_pwm_ops;
|
jz4740->chip.ops = &jz4740_pwm_ops;
|
||||||
jz4740->chip.npwm = NUM_PWM;
|
jz4740->chip.npwm = NUM_PWM;
|
||||||
jz4740->chip.base = -1;
|
jz4740->chip.base = -1;
|
||||||
|
jz4740->chip.of_xlate = of_pwm_xlate_with_flags;
|
||||||
|
jz4740->chip.of_pwm_n_cells = 3;
|
||||||
|
|
||||||
platform_set_drvdata(pdev, jz4740);
|
platform_set_drvdata(pdev, jz4740);
|
||||||
|
|
||||||
|
@ -162,9 +190,20 @@ static int jz4740_pwm_remove(struct platform_device *pdev)
|
||||||
return pwmchip_remove(&jz4740->chip);
|
return pwmchip_remove(&jz4740->chip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_OF
|
||||||
|
static const struct of_device_id jz4740_pwm_dt_ids[] = {
|
||||||
|
{ .compatible = "ingenic,jz4740-pwm", },
|
||||||
|
{ .compatible = "ingenic,jz4770-pwm", },
|
||||||
|
{ .compatible = "ingenic,jz4780-pwm", },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, jz4740_pwm_dt_ids);
|
||||||
|
#endif
|
||||||
|
|
||||||
static struct platform_driver jz4740_pwm_driver = {
|
static struct platform_driver jz4740_pwm_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "jz4740-pwm",
|
.name = "jz4740-pwm",
|
||||||
|
.of_match_table = of_match_ptr(jz4740_pwm_dt_ids),
|
||||||
},
|
},
|
||||||
.probe = jz4740_pwm_probe,
|
.probe = jz4740_pwm_probe,
|
||||||
.remove = jz4740_pwm_remove,
|
.remove = jz4740_pwm_remove,
|
||||||
|
|
|
@ -29,7 +29,9 @@
|
||||||
#define PWMGDUR 0x0c
|
#define PWMGDUR 0x0c
|
||||||
#define PWMWAVENUM 0x28
|
#define PWMWAVENUM 0x28
|
||||||
#define PWMDWIDTH 0x2c
|
#define PWMDWIDTH 0x2c
|
||||||
|
#define PWM45DWIDTH_FIXUP 0x30
|
||||||
#define PWMTHRES 0x30
|
#define PWMTHRES 0x30
|
||||||
|
#define PWM45THRES_FIXUP 0x34
|
||||||
|
|
||||||
#define PWM_CLK_DIV_MAX 7
|
#define PWM_CLK_DIV_MAX 7
|
||||||
|
|
||||||
|
@ -54,6 +56,7 @@ static const char * const mtk_pwm_clk_name[MTK_CLK_MAX] = {
|
||||||
|
|
||||||
struct mtk_pwm_platform_data {
|
struct mtk_pwm_platform_data {
|
||||||
unsigned int num_pwms;
|
unsigned int num_pwms;
|
||||||
|
bool pwm45_fixup;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,6 +69,7 @@ struct mtk_pwm_chip {
|
||||||
struct pwm_chip chip;
|
struct pwm_chip chip;
|
||||||
void __iomem *regs;
|
void __iomem *regs;
|
||||||
struct clk *clks[MTK_CLK_MAX];
|
struct clk *clks[MTK_CLK_MAX];
|
||||||
|
const struct mtk_pwm_platform_data *soc;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const unsigned int mtk_pwm_reg_offset[] = {
|
static const unsigned int mtk_pwm_reg_offset[] = {
|
||||||
|
@ -131,18 +135,25 @@ static int mtk_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
{
|
{
|
||||||
struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip);
|
struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip);
|
||||||
struct clk *clk = pc->clks[MTK_CLK_PWM1 + pwm->hwpwm];
|
struct clk *clk = pc->clks[MTK_CLK_PWM1 + pwm->hwpwm];
|
||||||
u32 resolution, clkdiv = 0;
|
u32 clkdiv = 0, cnt_period, cnt_duty, reg_width = PWMDWIDTH,
|
||||||
|
reg_thres = PWMTHRES;
|
||||||
|
u64 resolution;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = mtk_pwm_clk_enable(chip, pwm);
|
ret = mtk_pwm_clk_enable(chip, pwm);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
resolution = NSEC_PER_SEC / clk_get_rate(clk);
|
/* Using resolution in picosecond gets accuracy higher */
|
||||||
|
resolution = (u64)NSEC_PER_SEC * 1000;
|
||||||
|
do_div(resolution, clk_get_rate(clk));
|
||||||
|
|
||||||
while (period_ns / resolution > 8191) {
|
cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution);
|
||||||
|
while (cnt_period > 8191) {
|
||||||
resolution *= 2;
|
resolution *= 2;
|
||||||
clkdiv++;
|
clkdiv++;
|
||||||
|
cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000,
|
||||||
|
resolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clkdiv > PWM_CLK_DIV_MAX) {
|
if (clkdiv > PWM_CLK_DIV_MAX) {
|
||||||
|
@ -151,9 +162,19 @@ static int mtk_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) {
|
||||||
|
/*
|
||||||
|
* PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
|
||||||
|
* from the other PWMs on MT7623.
|
||||||
|
*/
|
||||||
|
reg_width = PWM45DWIDTH_FIXUP;
|
||||||
|
reg_thres = PWM45THRES_FIXUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt_duty = DIV_ROUND_CLOSEST_ULL((u64)duty_ns * 1000, resolution);
|
||||||
mtk_pwm_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | clkdiv);
|
mtk_pwm_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | clkdiv);
|
||||||
mtk_pwm_writel(pc, pwm->hwpwm, PWMDWIDTH, period_ns / resolution);
|
mtk_pwm_writel(pc, pwm->hwpwm, reg_width, cnt_period);
|
||||||
mtk_pwm_writel(pc, pwm->hwpwm, PWMTHRES, duty_ns / resolution);
|
mtk_pwm_writel(pc, pwm->hwpwm, reg_thres, cnt_duty);
|
||||||
|
|
||||||
mtk_pwm_clk_disable(chip, pwm);
|
mtk_pwm_clk_disable(chip, pwm);
|
||||||
|
|
||||||
|
@ -211,6 +232,7 @@ static int mtk_pwm_probe(struct platform_device *pdev)
|
||||||
data = of_device_get_match_data(&pdev->dev);
|
data = of_device_get_match_data(&pdev->dev);
|
||||||
if (data == NULL)
|
if (data == NULL)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
pc->soc = data;
|
||||||
|
|
||||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
pc->regs = devm_ioremap_resource(&pdev->dev, res);
|
pc->regs = devm_ioremap_resource(&pdev->dev, res);
|
||||||
|
@ -251,14 +273,17 @@ static int mtk_pwm_remove(struct platform_device *pdev)
|
||||||
|
|
||||||
static const struct mtk_pwm_platform_data mt2712_pwm_data = {
|
static const struct mtk_pwm_platform_data mt2712_pwm_data = {
|
||||||
.num_pwms = 8,
|
.num_pwms = 8,
|
||||||
|
.pwm45_fixup = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct mtk_pwm_platform_data mt7622_pwm_data = {
|
static const struct mtk_pwm_platform_data mt7622_pwm_data = {
|
||||||
.num_pwms = 6,
|
.num_pwms = 6,
|
||||||
|
.pwm45_fixup = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct mtk_pwm_platform_data mt7623_pwm_data = {
|
static const struct mtk_pwm_platform_data mt7623_pwm_data = {
|
||||||
.num_pwms = 5,
|
.num_pwms = 5,
|
||||||
|
.pwm45_fixup = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct of_device_id mtk_pwm_of_match[] = {
|
static const struct of_device_id mtk_pwm_of_match[] = {
|
||||||
|
@ -280,5 +305,4 @@ static struct platform_driver mtk_pwm_driver = {
|
||||||
module_platform_driver(mtk_pwm_driver);
|
module_platform_driver(mtk_pwm_driver);
|
||||||
|
|
||||||
MODULE_AUTHOR("John Crispin <blogic@openwrt.org>");
|
MODULE_AUTHOR("John Crispin <blogic@openwrt.org>");
|
||||||
MODULE_ALIAS("platform:mtk-pwm");
|
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
|
|
|
@ -107,10 +107,8 @@ static int pwm_probe(struct platform_device *pdev)
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
puv3 = devm_kzalloc(&pdev->dev, sizeof(*puv3), GFP_KERNEL);
|
puv3 = devm_kzalloc(&pdev->dev, sizeof(*puv3), GFP_KERNEL);
|
||||||
if (puv3 == NULL) {
|
if (!puv3)
|
||||||
dev_err(&pdev->dev, "failed to allocate memory\n");
|
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
|
||||||
|
|
||||||
puv3->clk = devm_clk_get(&pdev->dev, "OST_CLK");
|
puv3->clk = devm_clk_get(&pdev->dev, "OST_CLK");
|
||||||
if (IS_ERR(puv3->clk))
|
if (IS_ERR(puv3->clk))
|
||||||
|
|
|
@ -134,16 +134,12 @@ static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, int duty_ns,
|
||||||
|
|
||||||
static int rcar_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
|
static int rcar_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
{
|
{
|
||||||
struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip);
|
return pm_runtime_get_sync(chip->dev);
|
||||||
|
|
||||||
return clk_prepare_enable(rp->clk);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rcar_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
static void rcar_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
{
|
{
|
||||||
struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip);
|
pm_runtime_put(chip->dev);
|
||||||
|
|
||||||
clk_disable_unprepare(rp->clk);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rcar_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
static int rcar_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
|
@ -156,8 +152,12 @@ static int rcar_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
if (div < 0)
|
if (div < 0)
|
||||||
return div;
|
return div;
|
||||||
|
|
||||||
/* Let the core driver set pwm->period if disabled and duty_ns == 0 */
|
/*
|
||||||
if (!pwm_is_enabled(pwm) && !duty_ns)
|
* Let the core driver set pwm->period if disabled and duty_ns == 0.
|
||||||
|
* But, this driver should prevent to set the new duty_ns if current
|
||||||
|
* duty_cycle is not set
|
||||||
|
*/
|
||||||
|
if (!pwm_is_enabled(pwm) && !duty_ns && !pwm->state.duty_cycle)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
rcar_pwm_update(rp, RCAR_PWMCR_SYNC, RCAR_PWMCR_SYNC, RCAR_PWMCR);
|
rcar_pwm_update(rp, RCAR_PWMCR_SYNC, RCAR_PWMCR_SYNC, RCAR_PWMCR);
|
||||||
|
@ -258,11 +258,53 @@ static const struct of_device_id rcar_pwm_of_table[] = {
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(of, rcar_pwm_of_table);
|
MODULE_DEVICE_TABLE(of, rcar_pwm_of_table);
|
||||||
|
|
||||||
|
#ifdef CONFIG_PM_SLEEP
|
||||||
|
static struct pwm_device *rcar_pwm_dev_to_pwm_dev(struct device *dev)
|
||||||
|
{
|
||||||
|
struct platform_device *pdev = to_platform_device(dev);
|
||||||
|
struct rcar_pwm_chip *rcar_pwm = platform_get_drvdata(pdev);
|
||||||
|
struct pwm_chip *chip = &rcar_pwm->chip;
|
||||||
|
|
||||||
|
return &chip->pwms[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rcar_pwm_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct pwm_device *pwm = rcar_pwm_dev_to_pwm_dev(dev);
|
||||||
|
|
||||||
|
if (!test_bit(PWMF_REQUESTED, &pwm->flags))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
pm_runtime_put(dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rcar_pwm_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct pwm_device *pwm = rcar_pwm_dev_to_pwm_dev(dev);
|
||||||
|
|
||||||
|
if (!test_bit(PWMF_REQUESTED, &pwm->flags))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
pm_runtime_get_sync(dev);
|
||||||
|
|
||||||
|
rcar_pwm_config(pwm->chip, pwm, pwm->state.duty_cycle,
|
||||||
|
pwm->state.period);
|
||||||
|
if (pwm_is_enabled(pwm))
|
||||||
|
rcar_pwm_enable(pwm->chip, pwm);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_PM_SLEEP */
|
||||||
|
static SIMPLE_DEV_PM_OPS(rcar_pwm_pm_ops, rcar_pwm_suspend, rcar_pwm_resume);
|
||||||
|
|
||||||
static struct platform_driver rcar_pwm_driver = {
|
static struct platform_driver rcar_pwm_driver = {
|
||||||
.probe = rcar_pwm_probe,
|
.probe = rcar_pwm_probe,
|
||||||
.remove = rcar_pwm_remove,
|
.remove = rcar_pwm_remove,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "pwm-rcar",
|
.name = "pwm-rcar",
|
||||||
|
.pm = &rcar_pwm_pm_ops,
|
||||||
.of_match_table = of_match_ptr(rcar_pwm_of_table),
|
.of_match_table = of_match_ptr(rcar_pwm_of_table),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
/*
|
/*
|
||||||
* STM32 Low-Power Timer PWM driver
|
* STM32 Low-Power Timer PWM driver
|
||||||
*
|
*
|
||||||
|
@ -5,8 +6,6 @@
|
||||||
*
|
*
|
||||||
* Author: Gerald Baeza <gerald.baeza@st.com>
|
* Author: Gerald Baeza <gerald.baeza@st.com>
|
||||||
*
|
*
|
||||||
* License terms: GNU General Public License (GPL), version 2
|
|
||||||
*
|
|
||||||
* Inspired by Gerald Baeza's pwm-stm32 driver
|
* Inspired by Gerald Baeza's pwm-stm32 driver
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -203,6 +202,8 @@ static int stm32_pwm_lp_probe(struct platform_device *pdev)
|
||||||
priv->chip.dev = &pdev->dev;
|
priv->chip.dev = &pdev->dev;
|
||||||
priv->chip.ops = &stm32_pwm_lp_ops;
|
priv->chip.ops = &stm32_pwm_lp_ops;
|
||||||
priv->chip.npwm = 1;
|
priv->chip.npwm = 1;
|
||||||
|
priv->chip.of_xlate = of_pwm_xlate_with_flags;
|
||||||
|
priv->chip.of_pwm_n_cells = 3;
|
||||||
|
|
||||||
ret = pwmchip_add(&priv->chip);
|
ret = pwmchip_add(&priv->chip);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
/*
|
/*
|
||||||
* Copyright (C) STMicroelectronics 2016
|
* Copyright (C) STMicroelectronics 2016
|
||||||
*
|
*
|
||||||
* Author: Gerald Baeza <gerald.baeza@st.com>
|
* Author: Gerald Baeza <gerald.baeza@st.com>
|
||||||
*
|
*
|
||||||
* License terms: GNU General Public License (GPL), version 2
|
|
||||||
*
|
|
||||||
* Inspired by timer-stm32.c from Maxime Coquelin
|
* Inspired by timer-stm32.c from Maxime Coquelin
|
||||||
* pwm-atmel.c from Bo Shen
|
* pwm-atmel.c from Bo Shen
|
||||||
*/
|
*/
|
||||||
|
@ -21,7 +20,7 @@
|
||||||
|
|
||||||
struct stm32_pwm {
|
struct stm32_pwm {
|
||||||
struct pwm_chip chip;
|
struct pwm_chip chip;
|
||||||
struct device *dev;
|
struct mutex lock; /* protect pwm config/enable */
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
struct regmap *regmap;
|
struct regmap *regmap;
|
||||||
u32 max_arr;
|
u32 max_arr;
|
||||||
|
@ -214,9 +213,23 @@ static int stm32_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int stm32_pwm_apply_locked(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
|
struct pwm_state *state)
|
||||||
|
{
|
||||||
|
struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* protect common prescaler for all active channels */
|
||||||
|
mutex_lock(&priv->lock);
|
||||||
|
ret = stm32_pwm_apply(chip, pwm, state);
|
||||||
|
mutex_unlock(&priv->lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct pwm_ops stm32pwm_ops = {
|
static const struct pwm_ops stm32pwm_ops = {
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.apply = stm32_pwm_apply,
|
.apply = stm32_pwm_apply_locked,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int stm32_pwm_set_breakinput(struct stm32_pwm *priv,
|
static int stm32_pwm_set_breakinput(struct stm32_pwm *priv,
|
||||||
|
@ -336,6 +349,7 @@ static int stm32_pwm_probe(struct platform_device *pdev)
|
||||||
if (!priv)
|
if (!priv)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
mutex_init(&priv->lock);
|
||||||
priv->regmap = ddata->regmap;
|
priv->regmap = ddata->regmap;
|
||||||
priv->clk = ddata->clk;
|
priv->clk = ddata->clk;
|
||||||
priv->max_arr = ddata->max_arr;
|
priv->max_arr = ddata->max_arr;
|
||||||
|
|
|
@ -73,7 +73,6 @@ static const u32 prescaler_table[] = {
|
||||||
|
|
||||||
struct sun4i_pwm_data {
|
struct sun4i_pwm_data {
|
||||||
bool has_prescaler_bypass;
|
bool has_prescaler_bypass;
|
||||||
bool has_rdy;
|
|
||||||
unsigned int npwm;
|
unsigned int npwm;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -117,7 +116,8 @@ static void sun4i_pwm_get_state(struct pwm_chip *chip,
|
||||||
|
|
||||||
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
|
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
|
||||||
|
|
||||||
if ((val == PWM_PRESCAL_MASK) && sun4i_pwm->data->has_prescaler_bypass)
|
if ((PWM_REG_PRESCAL(val, pwm->hwpwm) == PWM_PRESCAL_MASK) &&
|
||||||
|
sun4i_pwm->data->has_prescaler_bypass)
|
||||||
prescaler = 1;
|
prescaler = 1;
|
||||||
else
|
else
|
||||||
prescaler = prescaler_table[PWM_REG_PRESCAL(val, pwm->hwpwm)];
|
prescaler = prescaler_table[PWM_REG_PRESCAL(val, pwm->hwpwm)];
|
||||||
|
@ -130,7 +130,8 @@ static void sun4i_pwm_get_state(struct pwm_chip *chip,
|
||||||
else
|
else
|
||||||
state->polarity = PWM_POLARITY_INVERSED;
|
state->polarity = PWM_POLARITY_INVERSED;
|
||||||
|
|
||||||
if (val & BIT_CH(PWM_CLK_GATING | PWM_EN, pwm->hwpwm))
|
if ((val & BIT_CH(PWM_CLK_GATING | PWM_EN, pwm->hwpwm)) ==
|
||||||
|
BIT_CH(PWM_CLK_GATING | PWM_EN, pwm->hwpwm))
|
||||||
state->enabled = true;
|
state->enabled = true;
|
||||||
else
|
else
|
||||||
state->enabled = false;
|
state->enabled = false;
|
||||||
|
@ -311,52 +312,37 @@ static const struct pwm_ops sun4i_pwm_ops = {
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct sun4i_pwm_data sun4i_pwm_data_a10 = {
|
static const struct sun4i_pwm_data sun4i_pwm_dual_nobypass = {
|
||||||
.has_prescaler_bypass = false,
|
.has_prescaler_bypass = false,
|
||||||
.has_rdy = false,
|
|
||||||
.npwm = 2,
|
.npwm = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct sun4i_pwm_data sun4i_pwm_data_a10s = {
|
static const struct sun4i_pwm_data sun4i_pwm_dual_bypass = {
|
||||||
.has_prescaler_bypass = true,
|
.has_prescaler_bypass = true,
|
||||||
.has_rdy = true,
|
|
||||||
.npwm = 2,
|
.npwm = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct sun4i_pwm_data sun4i_pwm_data_a13 = {
|
static const struct sun4i_pwm_data sun4i_pwm_single_bypass = {
|
||||||
.has_prescaler_bypass = true,
|
.has_prescaler_bypass = true,
|
||||||
.has_rdy = true,
|
|
||||||
.npwm = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct sun4i_pwm_data sun4i_pwm_data_a20 = {
|
|
||||||
.has_prescaler_bypass = true,
|
|
||||||
.has_rdy = true,
|
|
||||||
.npwm = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct sun4i_pwm_data sun4i_pwm_data_h3 = {
|
|
||||||
.has_prescaler_bypass = true,
|
|
||||||
.has_rdy = true,
|
|
||||||
.npwm = 1,
|
.npwm = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct of_device_id sun4i_pwm_dt_ids[] = {
|
static const struct of_device_id sun4i_pwm_dt_ids[] = {
|
||||||
{
|
{
|
||||||
.compatible = "allwinner,sun4i-a10-pwm",
|
.compatible = "allwinner,sun4i-a10-pwm",
|
||||||
.data = &sun4i_pwm_data_a10,
|
.data = &sun4i_pwm_dual_nobypass,
|
||||||
}, {
|
}, {
|
||||||
.compatible = "allwinner,sun5i-a10s-pwm",
|
.compatible = "allwinner,sun5i-a10s-pwm",
|
||||||
.data = &sun4i_pwm_data_a10s,
|
.data = &sun4i_pwm_dual_bypass,
|
||||||
}, {
|
}, {
|
||||||
.compatible = "allwinner,sun5i-a13-pwm",
|
.compatible = "allwinner,sun5i-a13-pwm",
|
||||||
.data = &sun4i_pwm_data_a13,
|
.data = &sun4i_pwm_single_bypass,
|
||||||
}, {
|
}, {
|
||||||
.compatible = "allwinner,sun7i-a20-pwm",
|
.compatible = "allwinner,sun7i-a20-pwm",
|
||||||
.data = &sun4i_pwm_data_a20,
|
.data = &sun4i_pwm_dual_bypass,
|
||||||
}, {
|
}, {
|
||||||
.compatible = "allwinner,sun8i-h3-pwm",
|
.compatible = "allwinner,sun8i-h3-pwm",
|
||||||
.data = &sun4i_pwm_data_h3,
|
.data = &sun4i_pwm_single_bypass,
|
||||||
}, {
|
}, {
|
||||||
/* sentinel */
|
/* sentinel */
|
||||||
},
|
},
|
||||||
|
|
|
@ -273,7 +273,8 @@ static int pwm_export_child(struct device *parent, struct pwm_device *pwm)
|
||||||
ret = device_register(&export->child);
|
ret = device_register(&export->child);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
clear_bit(PWMF_EXPORTED, &pwm->flags);
|
clear_bit(PWMF_EXPORTED, &pwm->flags);
|
||||||
kfree(export);
|
put_device(&export->child);
|
||||||
|
export = NULL;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user