linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v5 0/7] mfd: ac100: Add support for X-Powers AC100 audio codec / RTC combo IC
@ 2016-07-08 14:33 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
                   ` (7 more replies)
  0 siblings, 8 replies; 22+ messages in thread
From: Chen-Yu Tsai @ 2016-07-08 14:33 UTC (permalink / raw)
  To: linux-arm-kernel

Hi everyone,

This series adds support for X-Powers' AC100 audio codec / RTC combo IC.
This chip is found on Allwinner A80 SoC based boards, and is also part
of the AXP813/AXP818 PMIC found with Allwinner A83T SoCs.

The series focuses on the RTC side of the chip. The audio codec will
be done later, once the digital audio interface of the SoC is supported.

Changes since v4:

  - Style changes to DT bindings.
  - Added commit message to DT bindings commit.

Changes since v3:

  - bulk write support for regmaps without raw formatting patch merged
    and thus dropped from series.

  - sun9i device node re-ordering patches merged and thus dropped from
    series.

  - Fixed device node name and added labels to device tree binding example.

  - Moved 4M_adda clock to codec side, and added clock bindings to codec
    node.

  - Rename RTC clock output related prefixes from clk32k to clkout.

  - Rename default clock output names to cko{1,2,3}-rtc.

  - Move struct tm to/from RTC conversion offsets inline with reg[]
    assignments

  - Drop separate mutex and use rtc->ops_lock in interrupt handler.

  - Use platform_get_irq instead of of_get_irq.

  - Add struct of_device_id and .of_match_table to RTC driver.

Changes since v2:

  - Fix ac100_codec interrupt line in DT binding example.

  - Fix copy-paste errors in RTC set_alarm. Alarm now works.

  - Drop file name from MFD driver.

  - Add / update copyright notices.

  - Change license of RTC driver to GPL v2, matching MFD driver.

  - Fix MFD driver MODULE_LICENSE to match license declaration in header.

Changes since v1:

  - Complete register definitions and regmap regions.

Patch 1 adds device tree bindings for the AC100.

Patch 2 adds the mfd driver for the AC100.

Patch 3 adds the RTC driver for the AC100.

Patch 4 adds RTC clk output support to the RTC driver.
This is a separate patch as the clk code is over 300 LoC. Having a
separate patch should make it easier for the clk maintainers to review.

Patch 5 adds the AC100 device nodes to the A80 Optimus board dts.

Patch 6 adds the AC100 device nodes to the Cubieboard4 board dts.

Patch 7 changes the fixed osc_32k clk to a fixed factor clk,
representing the 32k clk input pin on the SoC, and also hooks up the
ac100 rtc clk output to this clk in the board dts files.

I'm hoping we can merge the driver bits (patches 1~4) through the
mfd tree, with acks from the rtc maintainers for patches 3 and 4.


Regards
ChenYu


Chen-Yu Tsai (7):
  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
  ARM: dts: sun9i: a80-optimus: Add device node for AC100
  ARM: dts: sun9i: cubieboard4: Add device node for AC100
  ARM: dts: sun9i: Switch to the AC100 RTC clock outputs for osc32k

 Documentation/devicetree/bindings/mfd/ac100.txt |  54 ++
 arch/arm/boot/dts/sun9i-a80-cubieboard4.dts     |  29 ++
 arch/arm/boot/dts/sun9i-a80-optimus.dts         |  29 ++
 arch/arm/boot/dts/sun9i-a80.dtsi                |   9 +-
 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 +++++++
 11 files changed, 1080 insertions(+), 6 deletions(-)
 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

-- 
2.8.1

^ 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-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 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 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 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, &reg);
+
+	/* 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, &reg);
+
+	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, &reg);
+
+	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 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 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 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, &reg);
> +
> +	/* 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, &reg);
> +
> +	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, &reg);
> +
> +	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 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 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 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 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 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, &reg);
> > +
> > +	/* 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, &reg);
> > +
> > +	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, &reg);
> > +
> > +	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 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

* [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

* [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

* [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

end of thread, other threads:[~2017-02-06 18:15 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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-08-08 11:50   ` Lee Jones
2016-08-08 11:56     ` 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
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
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
2017-02-06  3:29     ` Chen-Yu Tsai
2017-02-06 18:15       ` Rask Ingemann Lambertsen
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
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 ` [PATCH v5 6/7] ARM: dts: sun9i: cubieboard4: " 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-07-11  6:50   ` Maxime Ripard
2016-07-12  2:02     ` Chen-Yu Tsai
2016-08-16  7:20       ` 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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).