* [PATCH v5 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC combo IC
2016-07-08 14:33 [PATCH v5 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
@ 2016-07-08 14:33 ` Chen-Yu Tsai
2016-08-08 11:50 ` Lee Jones
2016-07-08 14:33 ` [PATCH v5 2/7] mfd: ac100: Add driver for X-Powers AC100 audio codec / RTC " Chen-Yu Tsai
` (6 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: Chen-Yu Tsai @ 2016-07-08 14:33 UTC (permalink / raw)
To: linux-arm-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: Rob Herring <robh@kernel.org>
---
Changes since v4:
- Style changes suggested by Lee.
- Added commit message.
- Added Rob's ack.
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 | 54 +++++++++++++++++++++++++
1 file changed, 54 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..b8ef00667599
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/ac100.txt
@@ -0,0 +1,54 @@
+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 at 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] 22+ messages in thread
* [PATCH v5 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC combo IC
2016-07-08 14:33 ` [PATCH v5 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC " Chen-Yu Tsai
@ 2016-08-08 11:50 ` Lee Jones
2016-08-08 11:56 ` Lee Jones
0 siblings, 1 reply; 22+ messages in thread
From: Lee Jones @ 2016-08-08 11:50 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, 08 Jul 2016, Chen-Yu Tsai wrote:
> 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: Rob Herring <robh@kernel.org>
> ---
> Changes since v4:
>
> - Style changes suggested by Lee.
> - Added commit message.
> - Added Rob's ack.
>
> 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 | 54 +++++++++++++++++++++++++
> 1 file changed, 54 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mfd/ac100.txt
For my own reference:
Acked-by: Lee Jones <lee.jones@linaro.org>
> diff --git a/Documentation/devicetree/bindings/mfd/ac100.txt b/Documentation/devicetree/bindings/mfd/ac100.txt
> new file mode 100644
> index 000000000000..b8ef00667599
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/ac100.txt
> @@ -0,0 +1,54 @@
> +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 at 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] 22+ messages in thread
* [PATCH v5 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC combo IC
2016-08-08 11:50 ` Lee Jones
@ 2016-08-08 11:56 ` Lee Jones
0 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-08-08 11:56 UTC (permalink / raw)
To: linux-arm-kernel
On Mon, 08 Aug 2016, Lee Jones wrote:
> On Fri, 08 Jul 2016, Chen-Yu Tsai wrote:
>
> > 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: Rob Herring <robh@kernel.org>
> > ---
> > Changes since v4:
> >
> > - Style changes suggested by Lee.
> > - Added commit message.
> > - Added Rob's ack.
> >
> > 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 | 54 +++++++++++++++++++++++++
> > 1 file changed, 54 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/mfd/ac100.txt
>
> For my own reference:
> Acked-by: Lee Jones <lee.jones@linaro.org>
Applied, thanks.
> > diff --git a/Documentation/devicetree/bindings/mfd/ac100.txt b/Documentation/devicetree/bindings/mfd/ac100.txt
> > new file mode 100644
> > index 000000000000..b8ef00667599
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/mfd/ac100.txt
> > @@ -0,0 +1,54 @@
> > +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 at 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] 22+ messages in thread
* [PATCH v5 2/7] mfd: ac100: Add driver for X-Powers AC100 audio codec / RTC combo IC
2016-07-08 14:33 [PATCH v5 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
2016-07-08 14:33 ` [PATCH v5 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC " Chen-Yu Tsai
@ 2016-07-08 14:33 ` Chen-Yu Tsai
2016-08-08 11:56 ` Lee Jones
2016-07-08 14:33 ` [PATCH v5 3/7] rtc: ac100: Add RTC driver for X-Powers AC100 Chen-Yu Tsai
` (5 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: Chen-Yu Tsai @ 2016-07-08 14:33 UTC (permalink / raw)
To: linux-arm-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 v4:
- None.
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] 22+ messages in thread
* [PATCH v5 2/7] mfd: ac100: Add driver for X-Powers AC100 audio codec / RTC combo IC
2016-07-08 14:33 ` [PATCH v5 2/7] mfd: ac100: Add driver for X-Powers AC100 audio codec / RTC " Chen-Yu Tsai
@ 2016-08-08 11:56 ` Lee Jones
0 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-08-08 11:56 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, 08 Jul 2016, Chen-Yu Tsai wrote:
> 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 v4:
>
> - None.
>
> 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
Applied, thanks.
> 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 */
--
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] 22+ messages in thread
* [PATCH v5 3/7] rtc: ac100: Add RTC driver for X-Powers AC100
2016-07-08 14:33 [PATCH v5 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
2016-07-08 14:33 ` [PATCH v5 1/7] mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC " Chen-Yu Tsai
2016-07-08 14:33 ` [PATCH v5 2/7] mfd: ac100: Add driver for X-Powers AC100 audio codec / RTC " Chen-Yu Tsai
@ 2016-07-08 14:33 ` Chen-Yu Tsai
2016-07-08 16:21 ` Alexandre Belloni
2017-02-05 21:33 ` [v5,3/7] " Rask Ingemann Lambertsen
2016-07-08 14:33 ` [PATCH v5 4/7] rtc: ac100: Add clk output support Chen-Yu Tsai
` (4 subsequent siblings)
7 siblings, 2 replies; 22+ messages in thread
From: Chen-Yu Tsai @ 2016-07-08 14:33 UTC (permalink / raw)
To: linux-arm-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 v4:
- None.
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] 22+ messages in thread
* [PATCH v5 3/7] rtc: ac100: Add RTC driver for X-Powers AC100
2016-07-08 14:33 ` [PATCH v5 3/7] rtc: ac100: Add RTC driver for X-Powers AC100 Chen-Yu Tsai
@ 2016-07-08 16:21 ` Alexandre Belloni
2016-08-08 11:57 ` Lee Jones
2017-02-05 21:33 ` [v5,3/7] " Rask Ingemann Lambertsen
1 sibling, 1 reply; 22+ messages in thread
From: Alexandre Belloni @ 2016-07-08 16:21 UTC (permalink / raw)
To: linux-arm-kernel
On 08/07/2016 at 22:33:38 +0800, Chen-Yu Tsai wrote :
> 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>
Acked-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
> ---
> Changes since v4:
>
> - None.
>
> 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
>
--
Alexandre Belloni, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v5 3/7] rtc: ac100: Add RTC driver for X-Powers AC100
2016-07-08 16:21 ` Alexandre Belloni
@ 2016-08-08 11:57 ` Lee Jones
0 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-08-08 11:57 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, 08 Jul 2016, Alexandre Belloni wrote:
> On 08/07/2016 at 22:33:38 +0800, Chen-Yu Tsai wrote :
> > 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>
> Acked-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
Applied, thanks.
> > ---
> > Changes since v4:
> >
> > - None.
> >
> > 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");
>
--
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] 22+ messages in thread
* [v5,3/7] rtc: ac100: Add RTC driver for X-Powers AC100
2016-07-08 14:33 ` [PATCH v5 3/7] rtc: ac100: Add RTC driver for X-Powers AC100 Chen-Yu Tsai
2016-07-08 16:21 ` Alexandre Belloni
@ 2017-02-05 21:33 ` Rask Ingemann Lambertsen
2017-02-06 3:29 ` Chen-Yu Tsai
1 sibling, 1 reply; 22+ messages in thread
From: Rask Ingemann Lambertsen @ 2017-02-05 21:33 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, Jul 08, 2016 at 10:33:38PM +0800, Chen-Yu Tsai wrote:
> X-Powers AC100 is a codec / RTC combo chip. This driver supports
> the RTC sub-device.
Don't we also want to build this driver by default, at least for
sunxi_defconfig, but possibly for multi_v7_defconfig as well?
-------------------- >8 --------------------
Subject: [PATCH] ARM: sunxi_defconfig: Enable AC100 RTC driver
Enable the AC100 RTC driver so boards with it can keep track of time.
Signed-off-by: Rask Ingemann Lambertsen <rask@formelder.dk>
---
arch/arm/configs/sunxi_defconfig | 2 ++
1 file changed, 2 insertions(+)
diff --git a/arch/arm/configs/sunxi_defconfig b/arch/arm/configs/sunxi_defconfig
index da92c25..5cd5dd70 100644
--- a/arch/arm/configs/sunxi_defconfig
+++ b/arch/arm/configs/sunxi_defconfig
@@ -87,6 +87,7 @@ CONFIG_THERMAL_OF=y
CONFIG_CPU_THERMAL=y
CONFIG_WATCHDOG=y
CONFIG_SUNXI_WATCHDOG=y
+CONFIG_MFD_AC100=y
CONFIG_MFD_AXP20X=y
CONFIG_MFD_AXP20X_I2C=y
CONFIG_MFD_AXP20X_RSB=y
@@ -129,6 +130,7 @@ CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
CONFIG_RTC_CLASS=y
# CONFIG_RTC_INTF_SYSFS is not set
# CONFIG_RTC_INTF_PROC is not set
+CONFIG_RTC_DRV_AC100=y
CONFIG_RTC_DRV_SUN6I=y
CONFIG_RTC_DRV_SUNXI=y
CONFIG_DMADEVICES=y
--
2.10.2
--
Rask Ingemann Lambertsen
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [v5,3/7] rtc: ac100: Add RTC driver for X-Powers AC100
2017-02-05 21:33 ` [v5,3/7] " Rask Ingemann Lambertsen
@ 2017-02-06 3:29 ` Chen-Yu Tsai
2017-02-06 18:15 ` Rask Ingemann Lambertsen
0 siblings, 1 reply; 22+ messages in thread
From: Chen-Yu Tsai @ 2017-02-06 3:29 UTC (permalink / raw)
To: linux-arm-kernel
On Mon, Feb 6, 2017 at 5:33 AM, Rask Ingemann Lambertsen
<rask@formelder.dk> wrote:
> On Fri, Jul 08, 2016 at 10:33:38PM +0800, Chen-Yu Tsai wrote:
>> X-Powers AC100 is a codec / RTC combo chip. This driver supports
>> the RTC sub-device.
>
> Don't we also want to build this driver by default, at least for
> sunxi_defconfig, but possibly for multi_v7_defconfig as well?
True.
> -------------------- >8 --------------------
> Subject: [PATCH] ARM: sunxi_defconfig: Enable AC100 RTC driver
>
> Enable the AC100 RTC driver so boards with it can keep track of time.
>
> Signed-off-by: Rask Ingemann Lambertsen <rask@formelder.dk>
Acked-by: Chen-Yu Tsai <wens@csie.org>
I suggest you send this in a new thread. Patches in replies tend
to get missed.
> ---
> arch/arm/configs/sunxi_defconfig | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/arch/arm/configs/sunxi_defconfig b/arch/arm/configs/sunxi_defconfig
> index da92c25..5cd5dd70 100644
> --- a/arch/arm/configs/sunxi_defconfig
> +++ b/arch/arm/configs/sunxi_defconfig
> @@ -87,6 +87,7 @@ CONFIG_THERMAL_OF=y
> CONFIG_CPU_THERMAL=y
> CONFIG_WATCHDOG=y
> CONFIG_SUNXI_WATCHDOG=y
> +CONFIG_MFD_AC100=y
> CONFIG_MFD_AXP20X=y
> CONFIG_MFD_AXP20X_I2C=y
> CONFIG_MFD_AXP20X_RSB=y
> @@ -129,6 +130,7 @@ CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
> CONFIG_RTC_CLASS=y
> # CONFIG_RTC_INTF_SYSFS is not set
> # CONFIG_RTC_INTF_PROC is not set
> +CONFIG_RTC_DRV_AC100=y
> CONFIG_RTC_DRV_SUN6I=y
> CONFIG_RTC_DRV_SUNXI=y
> CONFIG_DMADEVICES=y
> --
> 2.10.2
>
> --
> Rask Ingemann Lambertsen
^ permalink raw reply [flat|nested] 22+ messages in thread
* [v5,3/7] rtc: ac100: Add RTC driver for X-Powers AC100
2017-02-06 3:29 ` Chen-Yu Tsai
@ 2017-02-06 18:15 ` Rask Ingemann Lambertsen
0 siblings, 0 replies; 22+ messages in thread
From: Rask Ingemann Lambertsen @ 2017-02-06 18:15 UTC (permalink / raw)
To: linux-arm-kernel
On Mon, Feb 06, 2017 at 11:29:07AM +0800, Chen-Yu Tsai wrote:
> On Mon, Feb 6, 2017 at 5:33 AM, Rask Ingemann Lambertsen
> <rask@formelder.dk> wrote:
> > -------------------- >8 --------------------
> > Subject: [PATCH] ARM: sunxi_defconfig: Enable AC100 RTC driver
> >
> > Enable the AC100 RTC driver so boards with it can keep track of time.
> >
> > Signed-off-by: Rask Ingemann Lambertsen <rask@formelder.dk>
>
> Acked-by: Chen-Yu Tsai <wens@csie.org>
>
> I suggest you send this in a new thread. Patches in replies tend
> to get missed.
I will. Should I Cc: stable on this?
--
Rask Ingemann Lambertsen
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v5 4/7] rtc: ac100: Add clk output support
2016-07-08 14:33 [PATCH v5 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
` (2 preceding siblings ...)
2016-07-08 14:33 ` [PATCH v5 3/7] rtc: ac100: Add RTC driver for X-Powers AC100 Chen-Yu Tsai
@ 2016-07-08 14:33 ` Chen-Yu Tsai
2016-07-08 16:29 ` Alexandre Belloni
2016-07-08 14:33 ` [PATCH v5 5/7] ARM: dts: sun9i: a80-optimus: Add device node for AC100 Chen-Yu Tsai
` (3 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: Chen-Yu Tsai @ 2016-07-08 14:33 UTC (permalink / raw)
To: linux-arm-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 v4: none
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] 22+ messages in thread
* [PATCH v5 4/7] rtc: ac100: Add clk output support
2016-07-08 14:33 ` [PATCH v5 4/7] rtc: ac100: Add clk output support Chen-Yu Tsai
@ 2016-07-08 16:29 ` Alexandre Belloni
2016-08-08 11:56 ` Lee Jones
0 siblings, 1 reply; 22+ messages in thread
From: Alexandre Belloni @ 2016-07-08 16:29 UTC (permalink / raw)
To: linux-arm-kernel
On 08/07/2016 at 22:33:39 +0800, Chen-Yu Tsai wrote :
> 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>
Acked-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
> ---
> Changes since v4: none
>
> 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
>
--
Alexandre Belloni, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v5 4/7] rtc: ac100: Add clk output support
2016-07-08 16:29 ` Alexandre Belloni
@ 2016-08-08 11:56 ` Lee Jones
0 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-08-08 11:56 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, 08 Jul 2016, Alexandre Belloni wrote:
> On 08/07/2016 at 22:33:39 +0800, Chen-Yu Tsai wrote :
> > 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>
> Acked-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
>
> > ---
> > Changes since v4: none
> >
> > 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(+)
Applied, thanks.
> > 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),
>
--
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] 22+ messages in thread
* [PATCH v5 5/7] ARM: dts: sun9i: a80-optimus: Add device node for AC100
2016-07-08 14:33 [PATCH v5 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
` (3 preceding siblings ...)
2016-07-08 14:33 ` [PATCH v5 4/7] rtc: ac100: Add clk output support Chen-Yu Tsai
@ 2016-07-08 14:33 ` Chen-Yu Tsai
2016-07-08 14:33 ` [PATCH v5 6/7] ARM: dts: sun9i: cubieboard4: " Chen-Yu Tsai
` (2 subsequent siblings)
7 siblings, 0 replies; 22+ messages in thread
From: Chen-Yu Tsai @ 2016-07-08 14:33 UTC (permalink / raw)
To: linux-arm-kernel
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
Changes since v4: none
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 at 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] 22+ messages in thread
* [PATCH v5 6/7] ARM: dts: sun9i: cubieboard4: Add device node for AC100
2016-07-08 14:33 [PATCH v5 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
` (4 preceding siblings ...)
2016-07-08 14:33 ` [PATCH v5 5/7] ARM: dts: sun9i: a80-optimus: Add device node for AC100 Chen-Yu Tsai
@ 2016-07-08 14:33 ` Chen-Yu Tsai
2016-07-08 14:33 ` [PATCH v5 7/7] ARM: dts: sun9i: Switch to the AC100 RTC clock outputs for osc32k Chen-Yu Tsai
2016-08-08 12:01 ` [PATCH v5 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Lee Jones
7 siblings, 0 replies; 22+ messages in thread
From: Chen-Yu Tsai @ 2016-07-08 14:33 UTC (permalink / raw)
To: linux-arm-kernel
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
Changes since v4: none
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 at 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] 22+ messages in thread
* [PATCH v5 7/7] ARM: dts: sun9i: Switch to the AC100 RTC clock outputs for osc32k
2016-07-08 14:33 [PATCH v5 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
` (5 preceding siblings ...)
2016-07-08 14:33 ` [PATCH v5 6/7] ARM: dts: sun9i: cubieboard4: " Chen-Yu Tsai
@ 2016-07-08 14:33 ` Chen-Yu Tsai
2016-07-11 6:50 ` Maxime Ripard
2016-08-08 12:01 ` [PATCH v5 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Lee Jones
7 siblings, 1 reply; 22+ messages in thread
From: Chen-Yu Tsai @ 2016-07-08 14:33 UTC (permalink / raw)
To: linux-arm-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 v4: none
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 at 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 at 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] 22+ messages in thread
* [PATCH v5 7/7] ARM: dts: sun9i: Switch to the AC100 RTC clock outputs for osc32k
2016-07-08 14:33 ` [PATCH v5 7/7] ARM: dts: sun9i: Switch to the AC100 RTC clock outputs for osc32k Chen-Yu Tsai
@ 2016-07-11 6:50 ` Maxime Ripard
2016-07-12 2:02 ` Chen-Yu Tsai
0 siblings, 1 reply; 22+ messages in thread
From: Maxime Ripard @ 2016-07-11 6:50 UTC (permalink / raw)
To: linux-arm-kernel
Hi,
On Fri, Jul 08, 2016 at 10:33:42PM +0800, Chen-Yu Tsai wrote:
> 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 v4: none
> 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>;
> +};
> +
I'm guessing that an unresolved dependency when the driver has not
loaded yet, or is not even compiled ?
How is it working then?
Thanks,
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20160711/a5567557/attachment-0001.sig>
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v5 7/7] ARM: dts: sun9i: Switch to the AC100 RTC clock outputs for osc32k
2016-07-11 6:50 ` Maxime Ripard
@ 2016-07-12 2:02 ` Chen-Yu Tsai
2016-08-16 7:20 ` Chen-Yu Tsai
0 siblings, 1 reply; 22+ messages in thread
From: Chen-Yu Tsai @ 2016-07-12 2:02 UTC (permalink / raw)
To: linux-arm-kernel
On Mon, Jul 11, 2016 at 2:50 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> Hi,
>
> On Fri, Jul 08, 2016 at 10:33:42PM +0800, Chen-Yu Tsai wrote:
>> 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 v4: none
>> 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>;
>> +};
>> +
>
> I'm guessing that an unresolved dependency when the driver has not
> loaded yet, or is not even compiled ?
>
> How is it working then?
I assume the clk framework will leave it unresolved and unusable.
Also it seems that none of existing clks use it as a parent by
default. I will need to check the remaining unimplemented ones
though.
ChenYu
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v5 7/7] ARM: dts: sun9i: Switch to the AC100 RTC clock outputs for osc32k
2016-07-12 2:02 ` Chen-Yu Tsai
@ 2016-08-16 7:20 ` Chen-Yu Tsai
0 siblings, 0 replies; 22+ messages in thread
From: Chen-Yu Tsai @ 2016-08-16 7:20 UTC (permalink / raw)
To: linux-arm-kernel
On Tue, Jul 12, 2016 at 10:02 AM, Chen-Yu Tsai <wens@csie.org> wrote:
> On Mon, Jul 11, 2016 at 2:50 PM, Maxime Ripard
> <maxime.ripard@free-electrons.com> wrote:
>> Hi,
>>
>> On Fri, Jul 08, 2016 at 10:33:42PM +0800, Chen-Yu Tsai wrote:
>>> 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 v4: none
>>> 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>;
>>> +};
>>> +
>>
>> I'm guessing that an unresolved dependency when the driver has not
>> loaded yet, or is not even compiled ?
>>
>> How is it working then?
>
> I assume the clk framework will leave it unresolved and unusable.
> Also it seems that none of existing clks use it as a parent by
> default. I will need to check the remaining unimplemented ones
> though.
On the latest sunxi-next kernel, one clock is clocked from osc32k, which
becomes an orphaned clock:
clock enable_cnt prepare_cnt rate
accuracy phase
----------------------------------------------------------------------------------------
osc24M 6 6 24000000
0 0
r_ir 1 1 8000000
0 0
r_1wire 0 0 0
0 0
r_ir is clocked from osc32k by default, but the clk_set_rate call in the
IR driver seems to forces it to reparent to the working osc24M clock.
So I think we're good here. Can you merge the 3 remaining dts patches?
Thanks
ChenYu
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v5 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC
2016-07-08 14:33 [PATCH v5 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC Chen-Yu Tsai
` (6 preceding siblings ...)
2016-07-08 14:33 ` [PATCH v5 7/7] ARM: dts: sun9i: Switch to the AC100 RTC clock outputs for osc32k Chen-Yu Tsai
@ 2016-08-08 12:01 ` Lee Jones
7 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-08-08 12:01 UTC (permalink / raw)
To: linux-arm-kernel
Enjoy!
The following changes since commit 29b4817d4018df78086157ea3a55c1d9424a7cfc:
Linux 4.8-rc1 (2016-08-07 18:18:00 -0700)
are available in the git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git tags/ib-mfd-rtc-v4.9
for you to fetch changes up to 04940631b8d2b2e57a13b6d4ca50dfe5994b514f:
rtc: ac100: Add clk output support (2016-08-08 12:54:05 +0100)
----------------------------------------------------------------
Immutable branch between MFD and RTC, due for the v4.9 merge-window.
----------------------------------------------------------------
Chen-Yu Tsai (4):
mfd: ac100: Add device tree bindings for X-Powers AC100 codec/RTC combo IC
mfd: ac100: Add driver for X-Powers AC100 audio codec / RTC combo IC
rtc: ac100: Add RTC driver for X-Powers AC100
rtc: ac100: Add clk output support
Documentation/devicetree/bindings/mfd/ac100.txt | 54 ++
drivers/mfd/Kconfig | 10 +
drivers/mfd/Makefile | 2 +
drivers/mfd/ac100.c | 137 ++++++
drivers/rtc/Kconfig | 10 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-ac100.c | 627 ++++++++++++++++++++++++
include/linux/mfd/ac100.h | 178 +++++++
8 files changed, 1019 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/ac100.txt
create mode 100644 drivers/mfd/ac100.c
create mode 100644 drivers/rtc/rtc-ac100.c
create mode 100644 include/linux/mfd/ac100.h
--
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] 22+ messages in thread