* [PATCH v4 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC combo IC
2016-06-30 15:58 [PATCH v4 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
@ 2016-06-30 15:58 ` Chen-Yu Tsai
2016-07-01 2:48 ` Rob Herring
2016-07-01 8:40 ` Lee Jones
2016-06-30 15:58 ` [PATCH v4 2/7] mfd: ac100: Add driver for X-Powers AC100 audio codec / RTC " Chen-Yu Tsai
` (5 subsequent siblings)
6 siblings, 2 replies; 14+ messages in thread
From: Chen-Yu Tsai @ 2016-06-30 15:58 UTC (permalink / raw)
To: Lee Jones, Alessandro Zummo, Alexandre Belloni, Rob Herring,
Mark Rutland, Maxime Ripard, Michael Turquette, Stephen Boyd
Cc: Chen-Yu Tsai, linux-arm-kernel, rtc-linux, linux-clk, devicetree,
linux-kernel
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
Changes since v3:
- Fixed device node names and added labels.
- Added clock output from codec and input to rtc.
Changes since v2:
- Fix interrupt line for ac100_codec in provided example.
---
Documentation/devicetree/bindings/mfd/ac100.txt | 49 +++++++++++++++++++++++++
1 file changed, 49 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/ac100.txt
diff --git a/Documentation/devicetree/bindings/mfd/ac100.txt b/Documentation/devicetree/bindings/mfd/ac100.txt
new file mode 100644
index 000000000000..31e6f9cc41bc
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/ac100.txt
@@ -0,0 +1,49 @@
+X-Powers AC100 Codec/RTC IC device tree bindings
+
+AC100 is a audio codec and RTC subsystem combo IC. The 2 parts are
+separated, including power supplies and interrupt lines, but share
+a common register address space and host interface.
+
+Required properties:
+- compatible: "x-powers,ac100"
+- reg: The I2C slave address or RSB hardware address for the chip
+- sub-nodes:
+ - codec
+ - compatible: "x-powers,ac100-codec"
+ - interrupt-parent: The parent interrupt controller
+ - interrupts: SoC NMI / GPIO interrupt connected to the IRQ_AUDIO pin
+ - #clock-cells: shall be 0
+ - clock-output-names: "4M_adda"
+ - see clock/clock-bindings.txt for common clock bindings
+ - rtc
+ - compatible: "x-powers,ac100-rtc"
+ - interrupt-parent: The parent interrupt controller
+ - interrupts: SoC NMI / GPIO interrupt connected to the IRQ_RTC pin
+ - clocks: A phandle to the codec's "4M_adda" clock
+ - #clock-cells: shall be 1
+ - clock-output-names: "cko1_rtc", "cko2_rtc", "cko3_rtc"
+ - see clock/clock-bindings.txt for common clock bindings
+
+Example:
+
+ac100: codec@e89 {
+ compatible = "x-powers,ac100";
+ reg = <0xe89>;
+
+ ac100_codec: codec {
+ compatible = "x-powers,ac100-codec";
+ interrupt-parent = <&r_pio>;
+ interrupts = <0 9 IRQ_TYPE_LEVEL_LOW>; /* PL9 */
+ #clock-cells = <0>;
+ clock-output-names = "4M_adda";
+ };
+
+ ac100_rtc: rtc {
+ compatible = "x-powers,ac100-rtc";
+ interrupt-parent = <&nmi_intc>;
+ interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&ac100_codec>;
+ #clock-cells = <1>;
+ clock-output-names = "cko1_rtc", "cko2_rtc", "cko3_rtc";
+ };
+};
--
2.8.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v4 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC combo IC
2016-06-30 15:58 ` [PATCH v4 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC " Chen-Yu Tsai
@ 2016-07-01 2:48 ` Rob Herring
2016-07-01 8:40 ` Lee Jones
1 sibling, 0 replies; 14+ messages in thread
From: Rob Herring @ 2016-07-01 2:48 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Lee Jones, Alessandro Zummo, Alexandre Belloni, Mark Rutland,
Maxime Ripard, Michael Turquette, Stephen Boyd, linux-arm-kernel,
rtc-linux, linux-clk, devicetree, linux-kernel
On Thu, Jun 30, 2016 at 11:58:45PM +0800, Chen-Yu Tsai wrote:
> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
> ---
> Changes since v3:
>
> - Fixed device node names and added labels.
> - Added clock output from codec and input to rtc.
>
> Changes since v2:
>
> - Fix interrupt line for ac100_codec in provided example.
>
> ---
> Documentation/devicetree/bindings/mfd/ac100.txt | 49 +++++++++++++++++++++++++
> 1 file changed, 49 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mfd/ac100.txt
Acked-by: Rob Herring <robh@kernel.org>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC combo IC
2016-06-30 15:58 ` [PATCH v4 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC " Chen-Yu Tsai
2016-07-01 2:48 ` Rob Herring
@ 2016-07-01 8:40 ` Lee Jones
1 sibling, 0 replies; 14+ messages in thread
From: Lee Jones @ 2016-07-01 8:40 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Alessandro Zummo, Alexandre Belloni, Rob Herring, Mark Rutland,
Maxime Ripard, Michael Turquette, Stephen Boyd, linux-arm-kernel,
rtc-linux, linux-clk, devicetree, linux-kernel
On Thu, 30 Jun 2016, Chen-Yu Tsai wrote:
> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
> ---
> Changes since v3:
>
> - Fixed device node names and added labels.
> - Added clock output from codec and input to rtc.
>
> Changes since v2:
>
> - Fix interrupt line for ac100_codec in provided example.
>
> ---
> Documentation/devicetree/bindings/mfd/ac100.txt | 49 +++++++++++++++++++++++++
> 1 file changed, 49 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mfd/ac100.txt
>
> diff --git a/Documentation/devicetree/bindings/mfd/ac100.txt b/Documentation/devicetree/bindings/mfd/ac100.txt
> new file mode 100644
> index 000000000000..31e6f9cc41bc
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/ac100.txt
> @@ -0,0 +1,49 @@
> +X-Powers AC100 Codec/RTC IC device tree bindings
Nit: Device Tree
> +AC100 is a audio codec and RTC subsystem combo IC. The 2 parts are
> +separated, including power supplies and interrupt lines, but share
> +a common register address space and host interface.
> +
> +Required properties:
> +- compatible: "x-powers,ac100"
> +- reg: The I2C slave address or RSB hardware address for the chip
> +- sub-nodes:
> + - codec
> + - compatible: "x-powers,ac100-codec"
> + - interrupt-parent: The parent interrupt controller
> + - interrupts: SoC NMI / GPIO interrupt connected to the IRQ_AUDIO pin
> + - #clock-cells: shall be 0
Nit: Shall
> + - clock-output-names: "4M_adda"
> + - see clock/clock-bindings.txt for common clock bindings
- compatible: "x-powers,ac100-codec"
- interrupt-parent: The parent interrupt controller
- interrupts: SoC NMI / GPIO interrupt connected to the IRQ_AUDIO pin
- #clock-cells: shall be 0
- clock-output-names: "4M_adda"
- see clock/clock-bindings.txt for common clock bindings
Don't you think these are easier to read when they're separated?
> + - rtc
> + - compatible: "x-powers,ac100-rtc"
> + - interrupt-parent: The parent interrupt controller
> + - interrupts: SoC NMI / GPIO interrupt connected to the IRQ_RTC pin
> + - clocks: A phandle to the codec's "4M_adda" clock
> + - #clock-cells: shall be 1
Nit: Shall
> + - clock-output-names: "cko1_rtc", "cko2_rtc", "cko3_rtc"
> + - see clock/clock-bindings.txt for common clock bindings
Same here.
> +Example:
> +
> +ac100: codec@e89 {
> + compatible = "x-powers,ac100";
> + reg = <0xe89>;
> +
> + ac100_codec: codec {
> + compatible = "x-powers,ac100-codec";
> + interrupt-parent = <&r_pio>;
> + interrupts = <0 9 IRQ_TYPE_LEVEL_LOW>; /* PL9 */
> + #clock-cells = <0>;
> + clock-output-names = "4M_adda";
> + };
> +
> + ac100_rtc: rtc {
> + compatible = "x-powers,ac100-rtc";
> + interrupt-parent = <&nmi_intc>;
> + interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
> + clocks = <&ac100_codec>;
> + #clock-cells = <1>;
> + clock-output-names = "cko1_rtc", "cko2_rtc", "cko3_rtc";
> + };
> +};
--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 2/7] mfd: ac100: Add driver for X-Powers AC100 audio codec / RTC combo IC
2016-06-30 15:58 [PATCH v4 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
2016-06-30 15:58 ` [PATCH v4 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC " Chen-Yu Tsai
@ 2016-06-30 15:58 ` Chen-Yu Tsai
2016-06-30 15:58 ` [PATCH v4 3/7] rtc: ac100: Add RTC driver for X-Powers AC100 Chen-Yu Tsai
` (4 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Chen-Yu Tsai @ 2016-06-30 15:58 UTC (permalink / raw)
To: Lee Jones, Alessandro Zummo, Alexandre Belloni, Rob Herring,
Mark Rutland, Maxime Ripard, Michael Turquette, Stephen Boyd
Cc: Chen-Yu Tsai, linux-arm-kernel, rtc-linux, linux-clk, devicetree,
linux-kernel
The AC100 is a multifunction device with an audio codec subsystem and
an RTC subsystem. These two subsystems share a common register space
and host interface.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Lee Jones <lee.jones@linaro.org>
---
Changes since v3:
- Changed register name prefix to CLKOUT_ for clock output controls
Changes since v2:
- Dropped file name
- Added copyright line.
- Changed MODULE_LICENSE to GPL v2, matching the header
- Added Lee's ack
---
drivers/mfd/Kconfig | 10 +++
drivers/mfd/Makefile | 2 +
drivers/mfd/ac100.c | 137 +++++++++++++++++++++++++++++++++++
include/linux/mfd/ac100.h | 178 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 327 insertions(+)
create mode 100644 drivers/mfd/ac100.c
create mode 100644 include/linux/mfd/ac100.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 00ec1290b639..c2ceb303c856 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -112,6 +112,16 @@ config MFD_BCM590XX
help
Support for the BCM590xx PMUs from Broadcom
+config MFD_AC100
+ tristate "X-Powers AC100"
+ select MFD_CORE
+ depends on SUNXI_RSB
+ help
+ If you say Y here you get support for the X-Powers AC100 audio codec
+ IC.
+ This driver include only the core APIs. You have to select individual
+ components like codecs or RTC under the corresponding menus.
+
config MFD_AXP20X
tristate
select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 2ba3ba35f745..bca83dbb5ed8 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -113,6 +113,8 @@ obj-$(CONFIG_PMIC_DA9052) += da9052-irq.o
obj-$(CONFIG_PMIC_DA9052) += da9052-core.o
obj-$(CONFIG_MFD_DA9052_SPI) += da9052-spi.o
obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o
+
+obj-$(CONFIG_MFD_AC100) += ac100.o
obj-$(CONFIG_MFD_AXP20X) += axp20x.o
obj-$(CONFIG_MFD_AXP20X_I2C) += axp20x-i2c.o
obj-$(CONFIG_MFD_AXP20X_RSB) += axp20x-rsb.o
diff --git a/drivers/mfd/ac100.c b/drivers/mfd/ac100.c
new file mode 100644
index 000000000000..9bc69cd7807d
--- /dev/null
+++ b/drivers/mfd/ac100.c
@@ -0,0 +1,137 @@
+/*
+ * MFD core driver for X-Powers' AC100 Audio Codec IC
+ *
+ * The AC100 is a highly integrated audio codec and RTC subsystem designed
+ * for mobile applications. It has 3 I2S/PCM interfaces, a 2 channel DAC,
+ * a 2 channel ADC with 5 inputs and a builtin mixer. The RTC subsystem has
+ * 3 clock outputs.
+ *
+ * The audio codec and RTC parts are completely separate, sharing only the
+ * host interface for access to its registers.
+ *
+ * Copyright (2016) Chen-Yu Tsai
+ *
+ * Author: Chen-Yu Tsai <wens@csie.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/ac100.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/sunxi-rsb.h>
+
+static const struct regmap_range ac100_writeable_ranges[] = {
+ regmap_reg_range(AC100_CHIP_AUDIO_RST, AC100_I2S_SR_CTRL),
+ regmap_reg_range(AC100_I2S1_CLK_CTRL, AC100_I2S1_MXR_GAIN),
+ regmap_reg_range(AC100_I2S2_CLK_CTRL, AC100_I2S2_MXR_GAIN),
+ regmap_reg_range(AC100_I2S3_CLK_CTRL, AC100_I2S3_SIG_PATH_CTRL),
+ regmap_reg_range(AC100_ADC_DIG_CTRL, AC100_ADC_VOL_CTRL),
+ regmap_reg_range(AC100_HMIC_CTRL1, AC100_HMIC_STATUS),
+ regmap_reg_range(AC100_DAC_DIG_CTRL, AC100_DAC_MXR_GAIN),
+ regmap_reg_range(AC100_ADC_APC_CTRL, AC100_LINEOUT_CTRL),
+ regmap_reg_range(AC100_ADC_DAP_L_CTRL, AC100_ADC_DAP_OPT),
+ regmap_reg_range(AC100_DAC_DAP_CTRL, AC100_DAC_DAP_OPT),
+ regmap_reg_range(AC100_ADC_DAP_ENA, AC100_DAC_DAP_ENA),
+ regmap_reg_range(AC100_SRC1_CTRL1, AC100_SRC1_CTRL2),
+ regmap_reg_range(AC100_SRC2_CTRL1, AC100_SRC2_CTRL2),
+ regmap_reg_range(AC100_CLK32K_ANALOG_CTRL, AC100_CLKOUT_CTRL3),
+ regmap_reg_range(AC100_RTC_RST, AC100_RTC_UPD),
+ regmap_reg_range(AC100_ALM_INT_ENA, AC100_ALM_INT_STA),
+ regmap_reg_range(AC100_ALM_SEC, AC100_RTC_GP(15)),
+};
+
+static const struct regmap_range ac100_volatile_ranges[] = {
+ regmap_reg_range(AC100_CHIP_AUDIO_RST, AC100_PLL_CTRL2),
+ regmap_reg_range(AC100_HMIC_STATUS, AC100_HMIC_STATUS),
+ regmap_reg_range(AC100_ADC_DAP_L_STA, AC100_ADC_DAP_L_STA),
+ regmap_reg_range(AC100_SRC1_CTRL1, AC100_SRC1_CTRL1),
+ regmap_reg_range(AC100_SRC1_CTRL3, AC100_SRC2_CTRL1),
+ regmap_reg_range(AC100_SRC2_CTRL3, AC100_SRC2_CTRL4),
+ regmap_reg_range(AC100_RTC_RST, AC100_RTC_RST),
+ regmap_reg_range(AC100_RTC_SEC, AC100_ALM_INT_STA),
+ regmap_reg_range(AC100_ALM_SEC, AC100_ALM_UPD),
+};
+
+static const struct regmap_access_table ac100_writeable_table = {
+ .yes_ranges = ac100_writeable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(ac100_writeable_ranges),
+};
+
+static const struct regmap_access_table ac100_volatile_table = {
+ .yes_ranges = ac100_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(ac100_volatile_ranges),
+};
+
+static const struct regmap_config ac100_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .wr_table = &ac100_writeable_table,
+ .volatile_table = &ac100_volatile_table,
+ .max_register = AC100_RTC_GP(15),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static struct mfd_cell ac100_cells[] = {
+ {
+ .name = "ac100-codec",
+ .of_compatible = "x-powers,ac100-codec",
+ }, {
+ .name = "ac100-rtc",
+ .of_compatible = "x-powers,ac100-rtc",
+ },
+};
+
+static int ac100_rsb_probe(struct sunxi_rsb_device *rdev)
+{
+ struct ac100_dev *ac100;
+ int ret;
+
+ ac100 = devm_kzalloc(&rdev->dev, sizeof(*ac100), GFP_KERNEL);
+ if (!ac100)
+ return -ENOMEM;
+
+ ac100->dev = &rdev->dev;
+ sunxi_rsb_device_set_drvdata(rdev, ac100);
+
+ ac100->regmap = devm_regmap_init_sunxi_rsb(rdev, &ac100_regmap_config);
+ if (IS_ERR(ac100->regmap)) {
+ ret = PTR_ERR(ac100->regmap);
+ dev_err(ac100->dev, "regmap init failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_mfd_add_devices(ac100->dev, PLATFORM_DEVID_NONE, ac100_cells,
+ ARRAY_SIZE(ac100_cells), NULL, 0, NULL);
+ if (ret) {
+ dev_err(ac100->dev, "failed to add MFD devices: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id ac100_of_match[] = {
+ { .compatible = "x-powers,ac100" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ac100_of_match);
+
+static struct sunxi_rsb_driver ac100_rsb_driver = {
+ .driver = {
+ .name = "ac100",
+ .of_match_table = of_match_ptr(ac100_of_match),
+ },
+ .probe = ac100_rsb_probe,
+};
+module_sunxi_rsb_driver(ac100_rsb_driver);
+
+MODULE_DESCRIPTION("Audio codec MFD core driver for AC100");
+MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/ac100.h b/include/linux/mfd/ac100.h
new file mode 100644
index 000000000000..3c148f196b9f
--- /dev/null
+++ b/include/linux/mfd/ac100.h
@@ -0,0 +1,178 @@
+/*
+ * Functions and registers to access AC100 codec / RTC combo IC.
+ *
+ * Copyright (C) 2016 Chen-Yu Tsai
+ *
+ * Chen-Yu Tsai <wens@csie.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_MFD_AC100_H
+#define __LINUX_MFD_AC100_H
+
+#include <linux/regmap.h>
+
+struct ac100_dev {
+ struct device *dev;
+ struct regmap *regmap;
+};
+
+/* Audio codec related registers */
+#define AC100_CHIP_AUDIO_RST 0x00
+#define AC100_PLL_CTRL1 0x01
+#define AC100_PLL_CTRL2 0x02
+#define AC100_SYSCLK_CTRL 0x03
+#define AC100_MOD_CLK_ENA 0x04
+#define AC100_MOD_RST_CTRL 0x05
+#define AC100_I2S_SR_CTRL 0x06
+
+/* I2S1 interface */
+#define AC100_I2S1_CLK_CTRL 0x10
+#define AC100_I2S1_SND_OUT_CTRL 0x11
+#define AC100_I2S1_SND_IN_CTRL 0x12
+#define AC100_I2S1_MXR_SRC 0x13
+#define AC100_I2S1_VOL_CTRL1 0x14
+#define AC100_I2S1_VOL_CTRL2 0x15
+#define AC100_I2S1_VOL_CTRL3 0x16
+#define AC100_I2S1_VOL_CTRL4 0x17
+#define AC100_I2S1_MXR_GAIN 0x18
+
+/* I2S2 interface */
+#define AC100_I2S2_CLK_CTRL 0x20
+#define AC100_I2S2_SND_OUT_CTRL 0x21
+#define AC100_I2S2_SND_IN_CTRL 0x22
+#define AC100_I2S2_MXR_SRC 0x23
+#define AC100_I2S2_VOL_CTRL1 0x24
+#define AC100_I2S2_VOL_CTRL2 0x25
+#define AC100_I2S2_VOL_CTRL3 0x26
+#define AC100_I2S2_VOL_CTRL4 0x27
+#define AC100_I2S2_MXR_GAIN 0x28
+
+/* I2S3 interface */
+#define AC100_I2S3_CLK_CTRL 0x30
+#define AC100_I2S3_SND_OUT_CTRL 0x31
+#define AC100_I2S3_SND_IN_CTRL 0x32
+#define AC100_I2S3_SIG_PATH_CTRL 0x33
+
+/* ADC digital controls */
+#define AC100_ADC_DIG_CTRL 0x40
+#define AC100_ADC_VOL_CTRL 0x41
+
+/* HMIC plug sensing / key detection */
+#define AC100_HMIC_CTRL1 0x44
+#define AC100_HMIC_CTRL2 0x45
+#define AC100_HMIC_STATUS 0x46
+
+/* DAC digital controls */
+#define AC100_DAC_DIG_CTRL 0x48
+#define AC100_DAC_VOL_CTRL 0x49
+#define AC100_DAC_MXR_SRC 0x4c
+#define AC100_DAC_MXR_GAIN 0x4d
+
+/* Analog controls */
+#define AC100_ADC_APC_CTRL 0x50
+#define AC100_ADC_SRC 0x51
+#define AC100_ADC_SRC_BST_CTRL 0x52
+#define AC100_OUT_MXR_DAC_A_CTRL 0x53
+#define AC100_OUT_MXR_SRC 0x54
+#define AC100_OUT_MXR_SRC_BST 0x55
+#define AC100_HPOUT_CTRL 0x56
+#define AC100_ERPOUT_CTRL 0x57
+#define AC100_SPKOUT_CTRL 0x58
+#define AC100_LINEOUT_CTRL 0x59
+
+/* ADC digital audio processing (high pass filter & auto gain control */
+#define AC100_ADC_DAP_L_STA 0x80
+#define AC100_ADC_DAP_R_STA 0x81
+#define AC100_ADC_DAP_L_CTRL 0x82
+#define AC100_ADC_DAP_R_CTRL 0x83
+#define AC100_ADC_DAP_L_T_L 0x84 /* Left Target Level */
+#define AC100_ADC_DAP_R_T_L 0x85 /* Right Target Level */
+#define AC100_ADC_DAP_L_H_A_C 0x86 /* Left High Avg. Coef */
+#define AC100_ADC_DAP_L_L_A_C 0x87 /* Left Low Avg. Coef */
+#define AC100_ADC_DAP_R_H_A_C 0x88 /* Right High Avg. Coef */
+#define AC100_ADC_DAP_R_L_A_C 0x89 /* Right Low Avg. Coef */
+#define AC100_ADC_DAP_L_D_T 0x8a /* Left Decay Time */
+#define AC100_ADC_DAP_L_A_T 0x8b /* Left Attack Time */
+#define AC100_ADC_DAP_R_D_T 0x8c /* Right Decay Time */
+#define AC100_ADC_DAP_R_A_T 0x8d /* Right Attack Time */
+#define AC100_ADC_DAP_N_TH 0x8e /* Noise Threshold */
+#define AC100_ADC_DAP_L_H_N_A_C 0x8f /* Left High Noise Avg. Coef */
+#define AC100_ADC_DAP_L_L_N_A_C 0x90 /* Left Low Noise Avg. Coef */
+#define AC100_ADC_DAP_R_H_N_A_C 0x91 /* Right High Noise Avg. Coef */
+#define AC100_ADC_DAP_R_L_N_A_C 0x92 /* Right Low Noise Avg. Coef */
+#define AC100_ADC_DAP_H_HPF_C 0x93 /* High High-Pass-Filter Coef */
+#define AC100_ADC_DAP_L_HPF_C 0x94 /* Low High-Pass-Filter Coef */
+#define AC100_ADC_DAP_OPT 0x95 /* AGC Optimum */
+
+/* DAC digital audio processing (high pass filter & dynamic range control) */
+#define AC100_DAC_DAP_CTRL 0xa0
+#define AC100_DAC_DAP_H_HPF_C 0xa1 /* High High-Pass-Filter Coef */
+#define AC100_DAC_DAP_L_HPF_C 0xa2 /* Low High-Pass-Filter Coef */
+#define AC100_DAC_DAP_L_H_E_A_C 0xa3 /* Left High Energy Avg Coef */
+#define AC100_DAC_DAP_L_L_E_A_C 0xa4 /* Left Low Energy Avg Coef */
+#define AC100_DAC_DAP_R_H_E_A_C 0xa5 /* Right High Energy Avg Coef */
+#define AC100_DAC_DAP_R_L_E_A_C 0xa6 /* Right Low Energy Avg Coef */
+#define AC100_DAC_DAP_H_G_D_T_C 0xa7 /* High Gain Delay Time Coef */
+#define AC100_DAC_DAP_L_G_D_T_C 0xa8 /* Low Gain Delay Time Coef */
+#define AC100_DAC_DAP_H_G_A_T_C 0xa9 /* High Gain Attack Time Coef */
+#define AC100_DAC_DAP_L_G_A_T_C 0xaa /* Low Gain Attack Time Coef */
+#define AC100_DAC_DAP_H_E_TH 0xab /* High Energy Threshold */
+#define AC100_DAC_DAP_L_E_TH 0xac /* Low Energy Threshold */
+#define AC100_DAC_DAP_H_G_K 0xad /* High Gain K parameter */
+#define AC100_DAC_DAP_L_G_K 0xae /* Low Gain K parameter */
+#define AC100_DAC_DAP_H_G_OFF 0xaf /* High Gain offset */
+#define AC100_DAC_DAP_L_G_OFF 0xb0 /* Low Gain offset */
+#define AC100_DAC_DAP_OPT 0xb1 /* DRC optimum */
+
+/* Digital audio processing enable */
+#define AC100_ADC_DAP_ENA 0xb4
+#define AC100_DAC_DAP_ENA 0xb5
+
+/* SRC control */
+#define AC100_SRC1_CTRL1 0xb8
+#define AC100_SRC1_CTRL2 0xb9
+#define AC100_SRC1_CTRL3 0xba
+#define AC100_SRC1_CTRL4 0xbb
+#define AC100_SRC2_CTRL1 0xbc
+#define AC100_SRC2_CTRL2 0xbd
+#define AC100_SRC2_CTRL3 0xbe
+#define AC100_SRC2_CTRL4 0xbf
+
+/* RTC clk control */
+#define AC100_CLK32K_ANALOG_CTRL 0xc0
+#define AC100_CLKOUT_CTRL1 0xc1
+#define AC100_CLKOUT_CTRL2 0xc2
+#define AC100_CLKOUT_CTRL3 0xc3
+
+/* RTC module */
+#define AC100_RTC_RST 0xc6
+#define AC100_RTC_CTRL 0xc7
+#define AC100_RTC_SEC 0xc8 /* second */
+#define AC100_RTC_MIN 0xc9 /* minute */
+#define AC100_RTC_HOU 0xca /* hour */
+#define AC100_RTC_WEE 0xcb /* weekday */
+#define AC100_RTC_DAY 0xcc /* day */
+#define AC100_RTC_MON 0xcd /* month */
+#define AC100_RTC_YEA 0xce /* year */
+#define AC100_RTC_UPD 0xcf /* update trigger */
+
+/* RTC alarm */
+#define AC100_ALM_INT_ENA 0xd0
+#define AC100_ALM_INT_STA 0xd1
+#define AC100_ALM_SEC 0xd8
+#define AC100_ALM_MIN 0xd9
+#define AC100_ALM_HOU 0xda
+#define AC100_ALM_WEE 0xdb
+#define AC100_ALM_DAY 0xdc
+#define AC100_ALM_MON 0xdd
+#define AC100_ALM_YEA 0xde
+#define AC100_ALM_UPD 0xdf
+
+/* RTC general purpose register 0 ~ 15 */
+#define AC100_RTC_GP(x) (0xe0 + (x))
+
+#endif /* __LINUX_MFD_AC100_H */
--
2.8.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v4 3/7] rtc: ac100: Add RTC driver for X-Powers AC100
2016-06-30 15:58 [PATCH v4 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
2016-06-30 15:58 ` [PATCH v4 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC " Chen-Yu Tsai
2016-06-30 15:58 ` [PATCH v4 2/7] mfd: ac100: Add driver for X-Powers AC100 audio codec / RTC " Chen-Yu Tsai
@ 2016-06-30 15:58 ` Chen-Yu Tsai
2016-06-30 15:58 ` [PATCH v4 4/7] rtc: ac100: Add clk output support Chen-Yu Tsai
` (3 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Chen-Yu Tsai @ 2016-06-30 15:58 UTC (permalink / raw)
To: Lee Jones, Alessandro Zummo, Alexandre Belloni, Rob Herring,
Mark Rutland, Maxime Ripard, Michael Turquette, Stephen Boyd
Cc: Chen-Yu Tsai, linux-arm-kernel, rtc-linux, linux-clk, devicetree,
linux-kernel
X-Powers AC100 is a codec / RTC combo chip. This driver supports
the RTC sub-device.
The RTC block also has clock outputs and non-volatile storage.
Non-volatile storage wthin the RTC hardware is not supported.
Clock output support is added in the next patch.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
Changes since v3:
- Moved month/year conversion offsets inline with reg[] assignments
- Used platform_get_irq instead of of_get_irq
- Dropped separate mutex and use rtc->ops_lock in interrupt handler
- Dropped of_irq.h from includes
- Added of_device_id and .of_match_table
Changes since v2:
- Fixed off-by-1 errors in register mask macros
- Fixed copy-paste error in set_alarm function
- Drop uie_unsupported, since the alarm works now
- Fixed up copyright notice
- Changed license to GPL v2, matching mfd driver
---
drivers/rtc/Kconfig | 10 ++
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-ac100.c | 325 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 336 insertions(+)
create mode 100644 drivers/rtc/rtc-ac100.c
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 18639e0cb6e2..b9d7cbb6bd76 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -183,6 +183,16 @@ config RTC_DRV_ABX80X
This driver can also be built as a module. If so, the module
will be called rtc-abx80x.
+config RTC_DRV_AC100
+ tristate "X-Powers AC100"
+ depends on MFD_AC100
+ help
+ If you say yes here you get support for the real-time clock found
+ in X-Powers AC100 family peripheral ICs.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-ac100.
+
config RTC_DRV_AS3722
tristate "ams AS3722 RTC driver"
depends on MFD_AS3722
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index ea2833723fa9..b07c28779573 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_RTC_DRV_AB3100) += rtc-ab3100.o
obj-$(CONFIG_RTC_DRV_AB8500) += rtc-ab8500.o
obj-$(CONFIG_RTC_DRV_ABB5ZES3) += rtc-ab-b5ze-s3.o
obj-$(CONFIG_RTC_DRV_ABX80X) += rtc-abx80x.o
+obj-$(CONFIG_RTC_DRV_AC100) += rtc-ac100.o
obj-$(CONFIG_RTC_DRV_ARMADA38X) += rtc-armada38x.o
obj-$(CONFIG_RTC_DRV_AS3722) += rtc-as3722.o
obj-$(CONFIG_RTC_DRV_ASM9260) += rtc-asm9260.o
diff --git a/drivers/rtc/rtc-ac100.c b/drivers/rtc/rtc-ac100.c
new file mode 100644
index 000000000000..5a9ca89d04c7
--- /dev/null
+++ b/drivers/rtc/rtc-ac100.c
@@ -0,0 +1,325 @@
+/*
+ * RTC Driver for X-Powers AC100
+ *
+ * Copyright (c) 2016 Chen-Yu Tsai
+ *
+ * Chen-Yu Tsai <wens@csie.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/bcd.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/ac100.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+#include <linux/types.h>
+
+/* Control register */
+#define AC100_RTC_CTRL_24HOUR BIT(0)
+
+/* RTC */
+#define AC100_RTC_SEC_MASK GENMASK(6, 0)
+#define AC100_RTC_MIN_MASK GENMASK(6, 0)
+#define AC100_RTC_HOU_MASK GENMASK(5, 0)
+#define AC100_RTC_WEE_MASK GENMASK(2, 0)
+#define AC100_RTC_DAY_MASK GENMASK(5, 0)
+#define AC100_RTC_MON_MASK GENMASK(4, 0)
+#define AC100_RTC_YEA_MASK GENMASK(7, 0)
+#define AC100_RTC_YEA_LEAP BIT(15)
+#define AC100_RTC_UPD_TRIGGER BIT(15)
+
+/* Alarm (wall clock) */
+#define AC100_ALM_INT_ENABLE BIT(0)
+
+#define AC100_ALM_SEC_MASK GENMASK(6, 0)
+#define AC100_ALM_MIN_MASK GENMASK(6, 0)
+#define AC100_ALM_HOU_MASK GENMASK(5, 0)
+#define AC100_ALM_WEE_MASK GENMASK(2, 0)
+#define AC100_ALM_DAY_MASK GENMASK(5, 0)
+#define AC100_ALM_MON_MASK GENMASK(4, 0)
+#define AC100_ALM_YEA_MASK GENMASK(7, 0)
+#define AC100_ALM_ENABLE_FLAG BIT(15)
+#define AC100_ALM_UPD_TRIGGER BIT(15)
+
+/*
+ * The year parameter passed to the driver is usually an offset relative to
+ * the year 1900. This macro is used to convert this offset to another one
+ * relative to the minimum year allowed by the hardware.
+ *
+ * The year range is 1970 - 2069. This range is selected to match Allwinner's
+ * driver.
+ */
+#define AC100_YEAR_MIN 1970
+#define AC100_YEAR_MAX 2069
+#define AC100_YEAR_OFF (AC100_YEAR_MIN - 1900)
+
+struct ac100_rtc_dev {
+ struct rtc_device *rtc;
+ struct device *dev;
+ struct regmap *regmap;
+ int irq;
+ unsigned long alarm;
+};
+
+static int ac100_rtc_get_time(struct device *dev, struct rtc_time *rtc_tm)
+{
+ struct ac100_rtc_dev *chip = dev_get_drvdata(dev);
+ struct regmap *regmap = chip->regmap;
+ u16 reg[7];
+ int ret;
+
+ ret = regmap_bulk_read(regmap, AC100_RTC_SEC, reg, 7);
+ if (ret)
+ return ret;
+
+ rtc_tm->tm_sec = bcd2bin(reg[0] & AC100_RTC_SEC_MASK);
+ rtc_tm->tm_min = bcd2bin(reg[1] & AC100_RTC_MIN_MASK);
+ rtc_tm->tm_hour = bcd2bin(reg[2] & AC100_RTC_HOU_MASK);
+ rtc_tm->tm_wday = bcd2bin(reg[3] & AC100_RTC_WEE_MASK);
+ rtc_tm->tm_mday = bcd2bin(reg[4] & AC100_RTC_DAY_MASK);
+ rtc_tm->tm_mon = bcd2bin(reg[5] & AC100_RTC_MON_MASK) - 1;
+ rtc_tm->tm_year = bcd2bin(reg[6] & AC100_RTC_YEA_MASK) +
+ AC100_YEAR_OFF;
+
+ return rtc_valid_tm(rtc_tm);
+}
+
+static int ac100_rtc_set_time(struct device *dev, struct rtc_time *rtc_tm)
+{
+ struct ac100_rtc_dev *chip = dev_get_drvdata(dev);
+ struct regmap *regmap = chip->regmap;
+ int year;
+ u16 reg[8];
+
+ /* our RTC has a limited year range... */
+ year = rtc_tm->tm_year - AC100_YEAR_OFF;
+ if (year < 0 || year > (AC100_YEAR_MAX - 1900)) {
+ dev_err(dev, "rtc only supports year in range %d - %d\n",
+ AC100_YEAR_MIN, AC100_YEAR_MAX);
+ return -EINVAL;
+ }
+
+ /* convert to BCD */
+ reg[0] = bin2bcd(rtc_tm->tm_sec) & AC100_RTC_SEC_MASK;
+ reg[1] = bin2bcd(rtc_tm->tm_min) & AC100_RTC_MIN_MASK;
+ reg[2] = bin2bcd(rtc_tm->tm_hour) & AC100_RTC_HOU_MASK;
+ reg[3] = bin2bcd(rtc_tm->tm_wday) & AC100_RTC_WEE_MASK;
+ reg[4] = bin2bcd(rtc_tm->tm_mday) & AC100_RTC_DAY_MASK;
+ reg[5] = bin2bcd(rtc_tm->tm_mon + 1) & AC100_RTC_MON_MASK;
+ reg[6] = bin2bcd(year) & AC100_RTC_YEA_MASK;
+ /* trigger write */
+ reg[7] = AC100_RTC_UPD_TRIGGER;
+
+ /* Is it a leap year? */
+ if (is_leap_year(year + AC100_YEAR_OFF + 1900))
+ reg[6] |= AC100_RTC_YEA_LEAP;
+
+ return regmap_bulk_write(regmap, AC100_RTC_SEC, reg, 8);
+}
+
+static int ac100_rtc_alarm_irq_enable(struct device *dev, unsigned int en)
+{
+ struct ac100_rtc_dev *chip = dev_get_drvdata(dev);
+ struct regmap *regmap = chip->regmap;
+ unsigned int val;
+
+ val = en ? AC100_ALM_INT_ENABLE : 0;
+
+ return regmap_write(regmap, AC100_ALM_INT_ENA, val);
+}
+
+static int ac100_rtc_get_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct ac100_rtc_dev *chip = dev_get_drvdata(dev);
+ struct regmap *regmap = chip->regmap;
+ struct rtc_time *alrm_tm = &alrm->time;
+ u16 reg[7];
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(regmap, AC100_ALM_INT_ENA, &val);
+ if (ret)
+ return ret;
+
+ alrm->enabled = !!(val & AC100_ALM_INT_ENABLE);
+
+ ret = regmap_bulk_read(regmap, AC100_ALM_SEC, reg, 7);
+ if (ret)
+ return ret;
+
+ alrm_tm->tm_sec = bcd2bin(reg[0] & AC100_ALM_SEC_MASK);
+ alrm_tm->tm_min = bcd2bin(reg[1] & AC100_ALM_MIN_MASK);
+ alrm_tm->tm_hour = bcd2bin(reg[2] & AC100_ALM_HOU_MASK);
+ alrm_tm->tm_wday = bcd2bin(reg[3] & AC100_ALM_WEE_MASK);
+ alrm_tm->tm_mday = bcd2bin(reg[4] & AC100_ALM_DAY_MASK);
+ alrm_tm->tm_mon = bcd2bin(reg[5] & AC100_ALM_MON_MASK) - 1;
+ alrm_tm->tm_year = bcd2bin(reg[6] & AC100_ALM_YEA_MASK) +
+ AC100_YEAR_OFF;
+
+ return 0;
+}
+
+static int ac100_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct ac100_rtc_dev *chip = dev_get_drvdata(dev);
+ struct regmap *regmap = chip->regmap;
+ struct rtc_time *alrm_tm = &alrm->time;
+ u16 reg[8];
+ int year;
+ int ret;
+
+ /* our alarm has a limited year range... */
+ year = alrm_tm->tm_year - AC100_YEAR_OFF;
+ if (year < 0 || year > (AC100_YEAR_MAX - 1900)) {
+ dev_err(dev, "alarm only supports year in range %d - %d\n",
+ AC100_YEAR_MIN, AC100_YEAR_MAX);
+ return -EINVAL;
+ }
+
+ /* convert to BCD */
+ reg[0] = (bin2bcd(alrm_tm->tm_sec) & AC100_ALM_SEC_MASK) |
+ AC100_ALM_ENABLE_FLAG;
+ reg[1] = (bin2bcd(alrm_tm->tm_min) & AC100_ALM_MIN_MASK) |
+ AC100_ALM_ENABLE_FLAG;
+ reg[2] = (bin2bcd(alrm_tm->tm_hour) & AC100_ALM_HOU_MASK) |
+ AC100_ALM_ENABLE_FLAG;
+ /* Do not enable weekday alarm */
+ reg[3] = bin2bcd(alrm_tm->tm_wday) & AC100_ALM_WEE_MASK;
+ reg[4] = (bin2bcd(alrm_tm->tm_mday) & AC100_ALM_DAY_MASK) |
+ AC100_ALM_ENABLE_FLAG;
+ reg[5] = (bin2bcd(alrm_tm->tm_mon + 1) & AC100_ALM_MON_MASK) |
+ AC100_ALM_ENABLE_FLAG;
+ reg[6] = (bin2bcd(year) & AC100_ALM_YEA_MASK) |
+ AC100_ALM_ENABLE_FLAG;
+ /* trigger write */
+ reg[7] = AC100_ALM_UPD_TRIGGER;
+
+ ret = regmap_bulk_write(regmap, AC100_ALM_SEC, reg, 8);
+ if (ret)
+ return ret;
+
+ return ac100_rtc_alarm_irq_enable(dev, alrm->enabled);
+}
+
+static irqreturn_t ac100_rtc_irq(int irq, void *data)
+{
+ struct ac100_rtc_dev *chip = data;
+ struct regmap *regmap = chip->regmap;
+ unsigned int val = 0;
+ int ret;
+
+ mutex_lock(&chip->rtc->ops_lock);
+
+ /* read status */
+ ret = regmap_read(regmap, AC100_ALM_INT_STA, &val);
+ if (ret)
+ goto out;
+
+ if (val & AC100_ALM_INT_ENABLE) {
+ /* signal rtc framework */
+ rtc_update_irq(chip->rtc, 1, RTC_AF | RTC_IRQF);
+
+ /* clear status */
+ ret = regmap_write(regmap, AC100_ALM_INT_STA, val);
+ if (ret)
+ goto out;
+
+ /* disable interrupt */
+ ret = ac100_rtc_alarm_irq_enable(chip->dev, 0);
+ if (ret)
+ goto out;
+ }
+
+out:
+ mutex_unlock(&chip->rtc->ops_lock);
+ return IRQ_HANDLED;
+}
+
+static const struct rtc_class_ops ac100_rtc_ops = {
+ .read_time = ac100_rtc_get_time,
+ .set_time = ac100_rtc_set_time,
+ .read_alarm = ac100_rtc_get_alarm,
+ .set_alarm = ac100_rtc_set_alarm,
+ .alarm_irq_enable = ac100_rtc_alarm_irq_enable,
+};
+
+static int ac100_rtc_probe(struct platform_device *pdev)
+{
+ struct ac100_dev *ac100 = dev_get_drvdata(pdev->dev.parent);
+ struct ac100_rtc_dev *chip;
+ int ret;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ platform_set_drvdata(pdev, chip);
+ chip->dev = &pdev->dev;
+ chip->regmap = ac100->regmap;
+
+ chip->irq = platform_get_irq(pdev, 0);
+ if (chip->irq < 0) {
+ dev_err(&pdev->dev, "No IRQ resource\n");
+ return chip->irq;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, chip->irq, NULL,
+ ac100_rtc_irq,
+ IRQF_SHARED | IRQF_ONESHOT,
+ dev_name(&pdev->dev), chip);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not request IRQ\n");
+ return ret;
+ }
+
+ /* always use 24 hour mode */
+ regmap_write_bits(chip->regmap, AC100_RTC_CTRL, AC100_RTC_CTRL_24HOUR,
+ AC100_RTC_CTRL_24HOUR);
+
+ /* disable counter alarm interrupt */
+ regmap_write(chip->regmap, AC100_ALM_INT_ENA, 0);
+
+ /* clear counter alarm pending interrupts */
+ regmap_write(chip->regmap, AC100_ALM_INT_STA, AC100_ALM_INT_ENABLE);
+
+ chip->rtc = devm_rtc_device_register(&pdev->dev, "rtc-ac100",
+ &ac100_rtc_ops, THIS_MODULE);
+ if (IS_ERR(chip->rtc)) {
+ dev_err(&pdev->dev, "unable to register device\n");
+ return PTR_ERR(chip->rtc);
+ }
+
+ dev_info(&pdev->dev, "RTC enabled\n");
+
+ return 0;
+}
+
+static const struct of_device_id ac100_rtc_match[] = {
+ { .compatible = "x-powers,ac100-rtc" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ac100_rtc_match);
+
+static struct platform_driver ac100_rtc_driver = {
+ .probe = ac100_rtc_probe,
+ .driver = {
+ .name = "ac100-rtc",
+ .of_match_table = of_match_ptr(ac100_rtc_match),
+ },
+};
+module_platform_driver(ac100_rtc_driver);
+
+MODULE_DESCRIPTION("X-Powers AC100 RTC driver");
+MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
+MODULE_LICENSE("GPL v2");
--
2.8.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v4 4/7] rtc: ac100: Add clk output support
2016-06-30 15:58 [PATCH v4 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
` (2 preceding siblings ...)
2016-06-30 15:58 ` [PATCH v4 3/7] rtc: ac100: Add RTC driver for X-Powers AC100 Chen-Yu Tsai
@ 2016-06-30 15:58 ` Chen-Yu Tsai
2016-07-08 18:36 ` Michael Turquette
2016-06-30 15:58 ` [PATCH v4 5/7] ARM: dts: sun9i: a80-optimus: Add device node for AC100 Chen-Yu Tsai
` (2 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Chen-Yu Tsai @ 2016-06-30 15:58 UTC (permalink / raw)
To: Lee Jones, Alessandro Zummo, Alexandre Belloni, Rob Herring,
Mark Rutland, Maxime Ripard, Michael Turquette, Stephen Boyd
Cc: Chen-Yu Tsai, linux-arm-kernel, rtc-linux, linux-clk, devicetree,
linux-kernel
The AC100's RTC side has 3 clock outputs on external pins, which can
provide a clock signal to the SoC or other modules, such as WiFi or
GSM modules.
Support this with a custom clk driver integrated with the rtc driver.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
Changes since v3:
- Renamed clk32k prefixes to clkout, except for the internal 32k clk
- Changed default clk output names to "ac100-cko{1,2,3}-rtc"
- Moved 4M ADDA clk to codec side
Changes since v2: none
Changes since v1: none
---
drivers/rtc/rtc-ac100.c | 302 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 302 insertions(+)
diff --git a/drivers/rtc/rtc-ac100.c b/drivers/rtc/rtc-ac100.c
index 5a9ca89d04c7..70b4fd0f6122 100644
--- a/drivers/rtc/rtc-ac100.c
+++ b/drivers/rtc/rtc-ac100.c
@@ -16,6 +16,7 @@
*/
#include <linux/bcd.h>
+#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
@@ -31,6 +32,15 @@
/* Control register */
#define AC100_RTC_CTRL_24HOUR BIT(0)
+/* Clock output register bits */
+#define AC100_CLKOUT_PRE_DIV_SHIFT 5
+#define AC100_CLKOUT_PRE_DIV_WIDTH 3
+#define AC100_CLKOUT_MUX_SHIFT 4
+#define AC100_CLKOUT_MUX_WIDTH 1
+#define AC100_CLKOUT_DIV_SHIFT 1
+#define AC100_CLKOUT_DIV_WIDTH 3
+#define AC100_CLKOUT_EN BIT(0)
+
/* RTC */
#define AC100_RTC_SEC_MASK GENMASK(6, 0)
#define AC100_RTC_MIN_MASK GENMASK(6, 0)
@@ -67,14 +77,292 @@
#define AC100_YEAR_MAX 2069
#define AC100_YEAR_OFF (AC100_YEAR_MIN - 1900)
+struct ac100_clkout {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ u8 offset;
+};
+
+#define to_ac100_clkout(_hw) container_of(_hw, struct ac100_clkout, hw)
+
+#define AC100_RTC_32K_NAME "ac100-rtc-32k"
+#define AC100_RTC_32K_RATE 32768
+#define AC100_CLKOUT_NUM 3
+
+static const char * const ac100_clkout_names[AC100_CLKOUT_NUM] = {
+ "ac100-cko1-rtc",
+ "ac100-cko2-rtc",
+ "ac100-cko3-rtc",
+};
+
struct ac100_rtc_dev {
struct rtc_device *rtc;
struct device *dev;
struct regmap *regmap;
int irq;
unsigned long alarm;
+
+ struct clk_hw *rtc_32k_clk;
+ struct ac100_clkout clks[AC100_CLKOUT_NUM];
+ struct clk_hw_onecell_data *clk_data;
};
+/**
+ * Clock controls for 3 clock output pins
+ */
+
+static const struct clk_div_table ac100_clkout_prediv[] = {
+ { .val = 0, .div = 1 },
+ { .val = 1, .div = 2 },
+ { .val = 2, .div = 4 },
+ { .val = 3, .div = 8 },
+ { .val = 4, .div = 16 },
+ { .val = 5, .div = 32 },
+ { .val = 6, .div = 64 },
+ { .val = 7, .div = 122 },
+ { },
+};
+
+/* Abuse the fact that one parent is 32768 Hz, and the other is 4 MHz */
+static unsigned long ac100_clkout_recalc_rate(struct clk_hw *hw,
+ unsigned long prate)
+{
+ struct ac100_clkout *clk = to_ac100_clkout(hw);
+ unsigned int reg, div;
+
+ regmap_read(clk->regmap, clk->offset, ®);
+
+ /* Handle pre-divider first */
+ if (prate != AC100_RTC_32K_RATE) {
+ div = (reg >> AC100_CLKOUT_PRE_DIV_SHIFT) &
+ ((1 << AC100_CLKOUT_PRE_DIV_WIDTH) - 1);
+ prate = divider_recalc_rate(hw, prate, div,
+ ac100_clkout_prediv, 0);
+ }
+
+ div = (reg >> AC100_CLKOUT_DIV_SHIFT) &
+ (BIT(AC100_CLKOUT_DIV_WIDTH) - 1);
+ return divider_recalc_rate(hw, prate, div, NULL,
+ CLK_DIVIDER_POWER_OF_TWO);
+}
+
+static long ac100_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ unsigned long best_rate = 0, tmp_rate, tmp_prate;
+ int i;
+
+ if (prate == AC100_RTC_32K_RATE)
+ return divider_round_rate(hw, rate, &prate, NULL,
+ AC100_CLKOUT_DIV_WIDTH,
+ CLK_DIVIDER_POWER_OF_TWO);
+
+ for (i = 0; ac100_clkout_prediv[i].div; i++) {
+ tmp_prate = DIV_ROUND_UP(prate, ac100_clkout_prediv[i].val);
+ tmp_rate = divider_round_rate(hw, rate, &tmp_prate, NULL,
+ AC100_CLKOUT_DIV_WIDTH,
+ CLK_DIVIDER_POWER_OF_TWO);
+
+ if (tmp_rate > rate)
+ continue;
+ if (rate - tmp_rate < best_rate - tmp_rate)
+ best_rate = tmp_rate;
+ }
+
+ return best_rate;
+}
+
+static int ac100_clkout_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_hw *best_parent;
+ unsigned long best = 0;
+ int i, num_parents = clk_hw_get_num_parents(hw);
+
+ for (i = 0; i < num_parents; i++) {
+ struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
+ unsigned long tmp, prate = clk_hw_get_rate(parent);
+
+ tmp = ac100_clkout_round_rate(hw, req->rate, prate);
+
+ if (tmp > req->rate)
+ continue;
+ if (req->rate - tmp < req->rate - best) {
+ best = tmp;
+ best_parent = parent;
+ }
+ }
+
+ if (!best)
+ return -EINVAL;
+
+ req->best_parent_hw = best_parent;
+ req->best_parent_rate = best;
+ req->rate = best;
+
+ return 0;
+}
+
+static int ac100_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ struct ac100_clkout *clk = to_ac100_clkout(hw);
+ int div = 0, pre_div = 0;
+
+ do {
+ div = divider_get_val(rate * ac100_clkout_prediv[pre_div].div,
+ prate, NULL, AC100_CLKOUT_DIV_WIDTH,
+ CLK_DIVIDER_POWER_OF_TWO);
+ if (div >= 0)
+ break;
+ } while (prate != AC100_RTC_32K_RATE &&
+ ac100_clkout_prediv[++pre_div].div);
+
+ if (div < 0)
+ return div;
+
+ pre_div = ac100_clkout_prediv[pre_div].val;
+
+ regmap_update_bits(clk->regmap, clk->offset,
+ ((1 << AC100_CLKOUT_DIV_WIDTH) - 1) << AC100_CLKOUT_DIV_SHIFT |
+ ((1 << AC100_CLKOUT_PRE_DIV_WIDTH) - 1) << AC100_CLKOUT_PRE_DIV_SHIFT,
+ (div - 1) << AC100_CLKOUT_DIV_SHIFT |
+ (pre_div - 1) << AC100_CLKOUT_PRE_DIV_SHIFT);
+
+ return 0;
+}
+
+static int ac100_clkout_prepare(struct clk_hw *hw)
+{
+ struct ac100_clkout *clk = to_ac100_clkout(hw);
+
+ return regmap_update_bits(clk->regmap, clk->offset, AC100_CLKOUT_EN,
+ AC100_CLKOUT_EN);
+}
+
+static void ac100_clkout_unprepare(struct clk_hw *hw)
+{
+ struct ac100_clkout *clk = to_ac100_clkout(hw);
+
+ regmap_update_bits(clk->regmap, clk->offset, AC100_CLKOUT_EN, 0);
+}
+
+static int ac100_clkout_is_prepared(struct clk_hw *hw)
+{
+ struct ac100_clkout *clk = to_ac100_clkout(hw);
+ unsigned int reg;
+
+ regmap_read(clk->regmap, clk->offset, ®);
+
+ return reg & AC100_CLKOUT_EN;
+}
+
+static u8 ac100_clkout_get_parent(struct clk_hw *hw)
+{
+ struct ac100_clkout *clk = to_ac100_clkout(hw);
+ unsigned int reg;
+
+ regmap_read(clk->regmap, clk->offset, ®);
+
+ return (reg >> AC100_CLKOUT_MUX_SHIFT) & 0x1;
+}
+
+static int ac100_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct ac100_clkout *clk = to_ac100_clkout(hw);
+
+ return regmap_update_bits(clk->regmap, clk->offset,
+ BIT(AC100_CLKOUT_MUX_SHIFT),
+ index ? BIT(AC100_CLKOUT_MUX_SHIFT) : 0);
+}
+
+static const struct clk_ops ac100_clkout_ops = {
+ .prepare = ac100_clkout_prepare,
+ .unprepare = ac100_clkout_unprepare,
+ .is_prepared = ac100_clkout_is_prepared,
+ .recalc_rate = ac100_clkout_recalc_rate,
+ .determine_rate = ac100_clkout_determine_rate,
+ .get_parent = ac100_clkout_get_parent,
+ .set_parent = ac100_clkout_set_parent,
+ .set_rate = ac100_clkout_set_rate,
+};
+
+static int ac100_rtc_register_clks(struct ac100_rtc_dev *chip)
+{
+ struct device_node *np = chip->dev->of_node;
+ const char *parents[2] = {AC100_RTC_32K_NAME};
+ int i, ret;
+
+ chip->clk_data = devm_kzalloc(chip->dev, sizeof(*chip->clk_data) +
+ sizeof(*chip->clk_data->hws) *
+ AC100_CLKOUT_NUM,
+ GFP_KERNEL);
+ if (!chip->clk_data)
+ return -ENOMEM;
+
+ chip->rtc_32k_clk = clk_hw_register_fixed_rate(chip->dev,
+ AC100_RTC_32K_NAME,
+ NULL, 0,
+ AC100_RTC_32K_RATE);
+ if (IS_ERR(chip->rtc_32k_clk)) {
+ ret = PTR_ERR(chip->rtc_32k_clk);
+ dev_err(chip->dev, "Failed to register RTC-32k clock: %d\n",
+ ret);
+ return ret;
+ }
+
+ parents[1] = of_clk_get_parent_name(np, 0);
+ if (!parents[1]) {
+ dev_err(chip->dev, "Failed to get ADDA 4M clock\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < AC100_CLKOUT_NUM; i++) {
+ struct ac100_clkout *clk = &chip->clks[i];
+ struct clk_init_data init = {
+ .name = ac100_clkout_names[i],
+ .ops = &ac100_clkout_ops,
+ .parent_names = parents,
+ .num_parents = ARRAY_SIZE(parents),
+ .flags = 0,
+ };
+
+ clk->regmap = chip->regmap;
+ clk->offset = AC100_CLKOUT_CTRL1 + i;
+ clk->hw.init = &init;
+
+ ret = devm_clk_hw_register(chip->dev, &clk->hw);
+ if (ret) {
+ dev_err(chip->dev, "Failed to register clk '%s': %d\n",
+ init.name, ret);
+ goto err_unregister_rtc_32k;
+ }
+
+ chip->clk_data->hws[i] = &clk->hw;
+ }
+
+ chip->clk_data->num = i;
+ ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, chip->clk_data);
+ if (ret)
+ goto err_unregister_rtc_32k;
+
+ return 0;
+
+err_unregister_rtc_32k:
+ clk_unregister_fixed_rate(chip->rtc_32k_clk->clk);
+
+ return ret;
+}
+
+static void ac100_rtc_unregister_clks(struct ac100_rtc_dev *chip)
+{
+ of_clk_del_provider(chip->dev->of_node);
+ clk_unregister_fixed_rate(chip->rtc_32k_clk->clk);
+}
+
+/**
+ * RTC related bits
+ */
static int ac100_rtc_get_time(struct device *dev, struct rtc_time *rtc_tm)
{
struct ac100_rtc_dev *chip = dev_get_drvdata(dev);
@@ -300,11 +588,24 @@ static int ac100_rtc_probe(struct platform_device *pdev)
return PTR_ERR(chip->rtc);
}
+ ret = ac100_rtc_register_clks(chip);
+ if (ret)
+ return ret;
+
dev_info(&pdev->dev, "RTC enabled\n");
return 0;
}
+static int ac100_rtc_remove(struct platform_device *pdev)
+{
+ struct ac100_rtc_dev *chip = platform_get_drvdata(pdev);
+
+ ac100_rtc_unregister_clks(chip);
+
+ return 0;
+}
+
static const struct of_device_id ac100_rtc_match[] = {
{ .compatible = "x-powers,ac100-rtc" },
{ },
@@ -313,6 +614,7 @@ MODULE_DEVICE_TABLE(of, ac100_rtc_match);
static struct platform_driver ac100_rtc_driver = {
.probe = ac100_rtc_probe,
+ .remove = ac100_rtc_remove,
.driver = {
.name = "ac100-rtc",
.of_match_table = of_match_ptr(ac100_rtc_match),
--
2.8.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v4 4/7] rtc: ac100: Add clk output support
2016-06-30 15:58 ` [PATCH v4 4/7] rtc: ac100: Add clk output support Chen-Yu Tsai
@ 2016-07-08 18:36 ` Michael Turquette
2016-07-14 3:34 ` Chen-Yu Tsai
0 siblings, 1 reply; 14+ messages in thread
From: Michael Turquette @ 2016-07-08 18:36 UTC (permalink / raw)
To: Chen-Yu Tsai, Lee Jones, Alessandro Zummo, Alexandre Belloni,
Rob Herring, Mark Rutland, Maxime Ripard, Stephen Boyd
Cc: Chen-Yu Tsai, linux-arm-kernel, rtc-linux, linux-clk, devicetree,
linux-kernel
Quoting Chen-Yu Tsai (2016-06-30 08:58:48)
> +static long ac100_clkout_round_rate(struct clk_hw *hw, unsigned long rat=
e,
> + unsigned long prate)
> +{
> + unsigned long best_rate =3D 0, tmp_rate, tmp_prate;
> + int i;
> +
> + if (prate =3D=3D AC100_RTC_32K_RATE)
> + return divider_round_rate(hw, rate, &prate, NULL,
> + AC100_CLKOUT_DIV_WIDTH,
> + CLK_DIVIDER_POWER_OF_TWO);
> +
> + for (i =3D 0; ac100_clkout_prediv[i].div; i++) {
> + tmp_prate =3D DIV_ROUND_UP(prate, ac100_clkout_prediv[i].=
val);
> + tmp_rate =3D divider_round_rate(hw, rate, &tmp_prate, NUL=
L,
> + AC100_CLKOUT_DIV_WIDTH,
> + CLK_DIVIDER_POWER_OF_TWO);
> +
> + if (tmp_rate > rate)
> + continue;
> + if (rate - tmp_rate < best_rate - tmp_rate)
> + best_rate =3D tmp_rate;
> + }
> +
> + return best_rate;
> +}
> +
> +static int ac100_clkout_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
> + struct clk_hw *best_parent;
> + unsigned long best =3D 0;
> + int i, num_parents =3D clk_hw_get_num_parents(hw);
> +
> + for (i =3D 0; i < num_parents; i++) {
> + struct clk_hw *parent =3D clk_hw_get_parent_by_index(hw, =
i);
> + unsigned long tmp, prate =3D clk_hw_get_rate(parent);
> +
> + tmp =3D ac100_clkout_round_rate(hw, req->rate, prate);
> +
> + if (tmp > req->rate)
> + continue;
> + if (req->rate - tmp < req->rate - best) {
> + best =3D tmp;
> + best_parent =3D parent;
> + }
> + }
> +
> + if (!best)
> + return -EINVAL;
> +
> + req->best_parent_hw =3D best_parent;
> + req->best_parent_rate =3D best;
> + req->rate =3D best;
> +
> + return 0;
> +}
You only need one of the two functions above. Keep the .determine_rate
callback and drop .round_rate.
Otherwise the rest of the clk support looks great to me. Feel free to
add:
Reviewed-by: Michael Turquette <mturquette@baylibre.com>
Regards,
Mike
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 4/7] rtc: ac100: Add clk output support
2016-07-08 18:36 ` Michael Turquette
@ 2016-07-14 3:34 ` Chen-Yu Tsai
2016-07-22 0:24 ` Michael Turquette
0 siblings, 1 reply; 14+ messages in thread
From: Chen-Yu Tsai @ 2016-07-14 3:34 UTC (permalink / raw)
To: Michael Turquette
Cc: Chen-Yu Tsai, Lee Jones, Alessandro Zummo, Alexandre Belloni,
Rob Herring, Mark Rutland, Maxime Ripard, Stephen Boyd,
linux-arm-kernel, rtc-linux, linux-clk, devicetree, linux-kernel
On Sat, Jul 9, 2016 at 2:36 AM, Michael Turquette
<mturquette@baylibre.com> wrote:
> Quoting Chen-Yu Tsai (2016-06-30 08:58:48)
>> +static long ac100_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
>> + unsigned long prate)
>> +{
>> + unsigned long best_rate = 0, tmp_rate, tmp_prate;
>> + int i;
>> +
>> + if (prate == AC100_RTC_32K_RATE)
>> + return divider_round_rate(hw, rate, &prate, NULL,
>> + AC100_CLKOUT_DIV_WIDTH,
>> + CLK_DIVIDER_POWER_OF_TWO);
>> +
>> + for (i = 0; ac100_clkout_prediv[i].div; i++) {
>> + tmp_prate = DIV_ROUND_UP(prate, ac100_clkout_prediv[i].val);
>> + tmp_rate = divider_round_rate(hw, rate, &tmp_prate, NULL,
>> + AC100_CLKOUT_DIV_WIDTH,
>> + CLK_DIVIDER_POWER_OF_TWO);
>> +
>> + if (tmp_rate > rate)
>> + continue;
>> + if (rate - tmp_rate < best_rate - tmp_rate)
>> + best_rate = tmp_rate;
>> + }
>> +
>> + return best_rate;
>> +}
>> +
>> +static int ac100_clkout_determine_rate(struct clk_hw *hw,
>> + struct clk_rate_request *req)
>> +{
>> + struct clk_hw *best_parent;
>> + unsigned long best = 0;
>> + int i, num_parents = clk_hw_get_num_parents(hw);
>> +
>> + for (i = 0; i < num_parents; i++) {
>> + struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
>> + unsigned long tmp, prate = clk_hw_get_rate(parent);
>> +
>> + tmp = ac100_clkout_round_rate(hw, req->rate, prate);
>> +
>> + if (tmp > req->rate)
>> + continue;
>> + if (req->rate - tmp < req->rate - best) {
>> + best = tmp;
>> + best_parent = parent;
>> + }
>> + }
>> +
>> + if (!best)
>> + return -EINVAL;
>> +
>> + req->best_parent_hw = best_parent;
>> + req->best_parent_rate = best;
>> + req->rate = best;
>> +
>> + return 0;
>> +}
>
> You only need one of the two functions above. Keep the .determine_rate
> callback and drop .round_rate.
Only .round_rate is set in the clk_ops. ac100_clkout_round_rate just
serves to split out some of the calculations done per parent.
> Otherwise the rest of the clk support looks great to me. Feel free to
> add:
>
> Reviewed-by: Michael Turquette <mturquette@baylibre.com>
Thanks!
ChenYu
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 4/7] rtc: ac100: Add clk output support
2016-07-14 3:34 ` Chen-Yu Tsai
@ 2016-07-22 0:24 ` Michael Turquette
2016-07-22 1:22 ` Chen-Yu Tsai
0 siblings, 1 reply; 14+ messages in thread
From: Michael Turquette @ 2016-07-22 0:24 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Chen-Yu Tsai, Lee Jones, Alessandro Zummo, Alexandre Belloni,
Rob Herring, Mark Rutland, Maxime Ripard, Stephen Boyd,
linux-arm-kernel, rtc-linux, linux-clk, devicetree, linux-kernel
Quoting Chen-Yu Tsai (2016-07-13 20:34:34)
> On Sat, Jul 9, 2016 at 2:36 AM, Michael Turquette
> <mturquette@baylibre.com> wrote:
> > Quoting Chen-Yu Tsai (2016-06-30 08:58:48)
> >> +static long ac100_clkout_round_rate(struct clk_hw *hw, unsigned long =
rate,
> >> + unsigned long prate)
> >> +{
> >> + unsigned long best_rate =3D 0, tmp_rate, tmp_prate;
> >> + int i;
> >> +
> >> + if (prate =3D=3D AC100_RTC_32K_RATE)
> >> + return divider_round_rate(hw, rate, &prate, NULL,
> >> + AC100_CLKOUT_DIV_WIDTH,
> >> + CLK_DIVIDER_POWER_OF_TWO);
> >> +
> >> + for (i =3D 0; ac100_clkout_prediv[i].div; i++) {
> >> + tmp_prate =3D DIV_ROUND_UP(prate, ac100_clkout_prediv[=
i].val);
> >> + tmp_rate =3D divider_round_rate(hw, rate, &tmp_prate, =
NULL,
> >> + AC100_CLKOUT_DIV_WIDTH,
> >> + CLK_DIVIDER_POWER_OF_TWO=
);
> >> +
> >> + if (tmp_rate > rate)
> >> + continue;
> >> + if (rate - tmp_rate < best_rate - tmp_rate)
> >> + best_rate =3D tmp_rate;
> >> + }
> >> +
> >> + return best_rate;
> >> +}
> >> +
> >> +static int ac100_clkout_determine_rate(struct clk_hw *hw,
> >> + struct clk_rate_request *req)
> >> +{
> >> + struct clk_hw *best_parent;
> >> + unsigned long best =3D 0;
> >> + int i, num_parents =3D clk_hw_get_num_parents(hw);
> >> +
> >> + for (i =3D 0; i < num_parents; i++) {
> >> + struct clk_hw *parent =3D clk_hw_get_parent_by_index(h=
w, i);
> >> + unsigned long tmp, prate =3D clk_hw_get_rate(parent);
> >> +
> >> + tmp =3D ac100_clkout_round_rate(hw, req->rate, prate);
> >> +
> >> + if (tmp > req->rate)
> >> + continue;
> >> + if (req->rate - tmp < req->rate - best) {
> >> + best =3D tmp;
> >> + best_parent =3D parent;
> >> + }
> >> + }
> >> +
> >> + if (!best)
> >> + return -EINVAL;
> >> +
> >> + req->best_parent_hw =3D best_parent;
> >> + req->best_parent_rate =3D best;
> >> + req->rate =3D best;
> >> +
> >> + return 0;
> >> +}
> >
> > You only need one of the two functions above. Keep the .determine_rate
> > callback and drop .round_rate.
> =
> Only .round_rate is set in the clk_ops. ac100_clkout_round_rate just
> serves to split out some of the calculations done per parent.
If you only use .round_rate, shouldn't you drop .determine_rate then?
Regards,
Mike
> =
> > Otherwise the rest of the clk support looks great to me. Feel free to
> > add:
> >
> > Reviewed-by: Michael Turquette <mturquette@baylibre.com>
> =
> Thanks!
> =
> ChenYu
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 4/7] rtc: ac100: Add clk output support
2016-07-22 0:24 ` Michael Turquette
@ 2016-07-22 1:22 ` Chen-Yu Tsai
0 siblings, 0 replies; 14+ messages in thread
From: Chen-Yu Tsai @ 2016-07-22 1:22 UTC (permalink / raw)
To: Michael Turquette
Cc: Chen-Yu Tsai, Lee Jones, Alessandro Zummo, Alexandre Belloni,
Rob Herring, Mark Rutland, Maxime Ripard, Stephen Boyd,
linux-arm-kernel, rtc-linux, linux-clk, devicetree, linux-kernel
On Fri, Jul 22, 2016 at 8:24 AM, Michael Turquette
<mturquette@baylibre.com> wrote:
> Quoting Chen-Yu Tsai (2016-07-13 20:34:34)
>> On Sat, Jul 9, 2016 at 2:36 AM, Michael Turquette
>> <mturquette@baylibre.com> wrote:
>> > Quoting Chen-Yu Tsai (2016-06-30 08:58:48)
>> >> +static long ac100_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
>> >> + unsigned long prate)
>> >> +{
>> >> + unsigned long best_rate = 0, tmp_rate, tmp_prate;
>> >> + int i;
>> >> +
>> >> + if (prate == AC100_RTC_32K_RATE)
>> >> + return divider_round_rate(hw, rate, &prate, NULL,
>> >> + AC100_CLKOUT_DIV_WIDTH,
>> >> + CLK_DIVIDER_POWER_OF_TWO);
>> >> +
>> >> + for (i = 0; ac100_clkout_prediv[i].div; i++) {
>> >> + tmp_prate = DIV_ROUND_UP(prate, ac100_clkout_prediv[i].val);
>> >> + tmp_rate = divider_round_rate(hw, rate, &tmp_prate, NULL,
>> >> + AC100_CLKOUT_DIV_WIDTH,
>> >> + CLK_DIVIDER_POWER_OF_TWO);
>> >> +
>> >> + if (tmp_rate > rate)
>> >> + continue;
>> >> + if (rate - tmp_rate < best_rate - tmp_rate)
>> >> + best_rate = tmp_rate;
>> >> + }
>> >> +
>> >> + return best_rate;
>> >> +}
>> >> +
>> >> +static int ac100_clkout_determine_rate(struct clk_hw *hw,
>> >> + struct clk_rate_request *req)
>> >> +{
>> >> + struct clk_hw *best_parent;
>> >> + unsigned long best = 0;
>> >> + int i, num_parents = clk_hw_get_num_parents(hw);
>> >> +
>> >> + for (i = 0; i < num_parents; i++) {
>> >> + struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
>> >> + unsigned long tmp, prate = clk_hw_get_rate(parent);
>> >> +
>> >> + tmp = ac100_clkout_round_rate(hw, req->rate, prate);
>> >> +
>> >> + if (tmp > req->rate)
>> >> + continue;
>> >> + if (req->rate - tmp < req->rate - best) {
>> >> + best = tmp;
>> >> + best_parent = parent;
>> >> + }
>> >> + }
>> >> +
>> >> + if (!best)
>> >> + return -EINVAL;
>> >> +
>> >> + req->best_parent_hw = best_parent;
>> >> + req->best_parent_rate = best;
>> >> + req->rate = best;
>> >> +
>> >> + return 0;
>> >> +}
>> >
>> > You only need one of the two functions above. Keep the .determine_rate
>> > callback and drop .round_rate.
>>
>> Only .round_rate is set in the clk_ops. ac100_clkout_round_rate just
>> serves to split out some of the calculations done per parent.
>
> If you only use .round_rate, shouldn't you drop .determine_rate then?
Oops! I meant we only set .determine_rate. ac100_clkout_round_rate is
called in the per parent loop in .determine_rate.
ChenYu
>
> Regards,
> Mike
>
>>
>> > Otherwise the rest of the clk support looks great to me. Feel free to
>> > add:
>> >
>> > Reviewed-by: Michael Turquette <mturquette@baylibre.com>
>>
>> Thanks!
>>
>> ChenYu
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 5/7] ARM: dts: sun9i: a80-optimus: Add device node for AC100
2016-06-30 15:58 [PATCH v4 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
` (3 preceding siblings ...)
2016-06-30 15:58 ` [PATCH v4 4/7] rtc: ac100: Add clk output support Chen-Yu Tsai
@ 2016-06-30 15:58 ` Chen-Yu Tsai
2016-06-30 15:58 ` [PATCH v4 6/7] ARM: dts: sun9i: cubieboard4: " Chen-Yu Tsai
2016-06-30 15:58 ` [PATCH v4 7/7] ARM: dts: sun9i: Switch to the AC100 RTC clock outputs for osc32k Chen-Yu Tsai
6 siblings, 0 replies; 14+ messages in thread
From: Chen-Yu Tsai @ 2016-06-30 15:58 UTC (permalink / raw)
To: Lee Jones, Alessandro Zummo, Alexandre Belloni, Rob Herring,
Mark Rutland, Maxime Ripard, Michael Turquette, Stephen Boyd
Cc: Chen-Yu Tsai, linux-arm-kernel, rtc-linux, linux-clk, devicetree,
linux-kernel
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
Changes since v3:
- Moved 4M ADDA clk to codec side
Changes since v2: none
Changes since v1: none
---
arch/arm/boot/dts/sun9i-a80-optimus.dts | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/arch/arm/boot/dts/sun9i-a80-optimus.dts b/arch/arm/boot/dts/sun9i-a80-optimus.dts
index 7fd22e888602..e35c06c162a7 100644
--- a/arch/arm/boot/dts/sun9i-a80-optimus.dts
+++ b/arch/arm/boot/dts/sun9i-a80-optimus.dts
@@ -322,6 +322,30 @@
};
};
};
+
+ ac100: codec@e89 {
+ compatible = "x-powers,ac100";
+ reg = <0xe89>;
+
+ ac100_codec: codec {
+ compatible = "x-powers,ac100-codec";
+ interrupt-parent = <&r_pio>;
+ interrupts = <0 9 IRQ_TYPE_LEVEL_LOW>; /* PL9 */
+ #clock-cells = <0>;
+ clock-output-names = "4M_adda";
+ };
+
+ ac100_rtc: rtc {
+ compatible = "x-powers,ac100-rtc";
+ interrupt-parent = <&nmi_intc>;
+ interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&ac100_codec>;
+ #clock-cells = <1>;
+ clock-output-names = "cko1_rtc",
+ "cko2_rtc",
+ "cko3_rtc";
+ };
+ };
};
#include "axp809.dtsi"
--
2.8.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v4 6/7] ARM: dts: sun9i: cubieboard4: Add device node for AC100
2016-06-30 15:58 [PATCH v4 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
` (4 preceding siblings ...)
2016-06-30 15:58 ` [PATCH v4 5/7] ARM: dts: sun9i: a80-optimus: Add device node for AC100 Chen-Yu Tsai
@ 2016-06-30 15:58 ` Chen-Yu Tsai
2016-06-30 15:58 ` [PATCH v4 7/7] ARM: dts: sun9i: Switch to the AC100 RTC clock outputs for osc32k Chen-Yu Tsai
6 siblings, 0 replies; 14+ messages in thread
From: Chen-Yu Tsai @ 2016-06-30 15:58 UTC (permalink / raw)
To: Lee Jones, Alessandro Zummo, Alexandre Belloni, Rob Herring,
Mark Rutland, Maxime Ripard, Michael Turquette, Stephen Boyd
Cc: Chen-Yu Tsai, linux-arm-kernel, rtc-linux, linux-clk, devicetree,
linux-kernel
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
Changes since v3:
- Moved 4M ADDA clk to codec side
Changes since v2: none
Changes since v1: none
---
arch/arm/boot/dts/sun9i-a80-cubieboard4.dts | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/arch/arm/boot/dts/sun9i-a80-cubieboard4.dts b/arch/arm/boot/dts/sun9i-a80-cubieboard4.dts
index 1526b41c70f1..cf2f4b72a841 100644
--- a/arch/arm/boot/dts/sun9i-a80-cubieboard4.dts
+++ b/arch/arm/boot/dts/sun9i-a80-cubieboard4.dts
@@ -250,6 +250,30 @@
};
};
};
+
+ ac100: codec@e89 {
+ compatible = "x-powers,ac100";
+ reg = <0xe89>;
+
+ ac100_codec: codec {
+ compatible = "x-powers,ac100-codec";
+ interrupt-parent = <&r_pio>;
+ interrupts = <0 9 IRQ_TYPE_LEVEL_LOW>; /* PL9 */
+ #clock-cells = <0>;
+ clock-output-names = "4M_adda";
+ };
+
+ ac100_rtc: rtc {
+ compatible = "x-powers,ac100-rtc";
+ interrupt-parent = <&nmi_intc>;
+ interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&ac100_codec>;
+ #clock-cells = <1>;
+ clock-output-names = "cko1_rtc",
+ "cko2_rtc",
+ "cko3_rtc";
+ };
+ };
};
#include "axp809.dtsi"
--
2.8.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v4 7/7] ARM: dts: sun9i: Switch to the AC100 RTC clock outputs for osc32k
2016-06-30 15:58 [PATCH v4 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
` (5 preceding siblings ...)
2016-06-30 15:58 ` [PATCH v4 6/7] ARM: dts: sun9i: cubieboard4: " Chen-Yu Tsai
@ 2016-06-30 15:58 ` Chen-Yu Tsai
6 siblings, 0 replies; 14+ messages in thread
From: Chen-Yu Tsai @ 2016-06-30 15:58 UTC (permalink / raw)
To: Lee Jones, Alessandro Zummo, Alexandre Belloni, Rob Herring,
Mark Rutland, Maxime Ripard, Michael Turquette, Stephen Boyd
Cc: Chen-Yu Tsai, linux-arm-kernel, rtc-linux, linux-clk, devicetree,
linux-kernel
The 32.768 kHz clock inside the A80 SoC is fed from an external source,
typically the AC100 RTC module.
Make the osc32k placeholder a fixed-factor clock so board dts files can
specify its source.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
Changes since v3: none
Changes since v2: none
Changes since v1: none
---
arch/arm/boot/dts/sun9i-a80-cubieboard4.dts | 5 +++++
arch/arm/boot/dts/sun9i-a80-optimus.dts | 5 +++++
arch/arm/boot/dts/sun9i-a80.dtsi | 9 +++------
3 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/arch/arm/boot/dts/sun9i-a80-cubieboard4.dts b/arch/arm/boot/dts/sun9i-a80-cubieboard4.dts
index cf2f4b72a841..04b014603659 100644
--- a/arch/arm/boot/dts/sun9i-a80-cubieboard4.dts
+++ b/arch/arm/boot/dts/sun9i-a80-cubieboard4.dts
@@ -103,6 +103,11 @@
allwinner,drive = <SUN4I_PINCTRL_40_MA>;
};
+&osc32k {
+ /* osc32k input is from AC100 */
+ clocks = <&ac100_rtc 0>;
+};
+
&pio {
led_pins_cubieboard4: led-pins@0 {
allwinner,pins = "PH6", "PH17";
diff --git a/arch/arm/boot/dts/sun9i-a80-optimus.dts b/arch/arm/boot/dts/sun9i-a80-optimus.dts
index e35c06c162a7..fd874ded890e 100644
--- a/arch/arm/boot/dts/sun9i-a80-optimus.dts
+++ b/arch/arm/boot/dts/sun9i-a80-optimus.dts
@@ -152,6 +152,11 @@
status = "okay";
};
+&osc32k {
+ /* osc32k input is from AC100 */
+ clocks = <&ac100_rtc 0>;
+};
+
&pio {
led_pins_optimus: led-pins@0 {
allwinner,pins = "PH0", "PH1";
diff --git a/arch/arm/boot/dts/sun9i-a80.dtsi b/arch/arm/boot/dts/sun9i-a80.dtsi
index f68b3242b33a..dd11115ec087 100644
--- a/arch/arm/boot/dts/sun9i-a80.dtsi
+++ b/arch/arm/boot/dts/sun9i-a80.dtsi
@@ -148,15 +148,12 @@
/*
* The 32k clock is from an external source, normally the
- * AC100 codec/RTC chip. This clock is by default enabled
- * and clocked at 32768 Hz, from the oscillator connected
- * to the AC100. It is configurable, but no such driver or
- * bindings exist yet.
+ * AC100 codec/RTC chip. This serves as a placeholder for
+ * board dts files to specify the source.
*/
osc32k: osc32k_clk {
#clock-cells = <0>;
- compatible = "fixed-clock";
- clock-frequency = <32768>;
+ compatible = "fixed-factor-clock";
clock-output-names = "osc32k";
};
--
2.8.1
^ permalink raw reply related [flat|nested] 14+ messages in thread