Discussion:
[PATCHv1 2/4] ARM: dts: socfpga: add a bypass-reg binding for Stratix10
Dinh Nguyen
2017-07-07 19:03:17 UTC
Permalink
Add a 'bypass-reg' binding property for the Stratix10 clock. There are quite a
few clocks on the Stratix10 platform that have a separate bypass setting from
the clock's original parent.

The 'bypass-reg' binding contains the bypass register offset from the clock
manager's base address and a bit index.

Signed-off-by: Dinh Nguyen <***@kernel.org>
---
Documentation/devicetree/bindings/clock/altr_socfpga.txt | 3 +++
1 file changed, 3 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/altr_socfpga.txt b/Documentation/devicetree/bindings/clock/altr_socfpga.txt
index 1c32658..9e2754a 100644
--- a/Documentation/devicetree/bindings/clock/altr_socfpga.txt
+++ b/Documentation/devicetree/bindings/clock/altr_socfpga.txt
@@ -42,3 +42,6 @@ Optional properties:
value is the cclk_in_drv(drvsel). The clk-phase is used to enable the correct
hold/delay times that is needed for the SD/MMC CIU clock. The values of both
can be 0-315 degrees, in 45 degree increments.
+- bypass-reg : There are a few clocks on the Stratix10 platform that can be
+ bypassed from their original parents to a separate clock. This binding
+ property contains the bypass register and the bit index.
--
2.7.4
Dinh Nguyen
2017-07-07 19:03:18 UTC
Permalink
Add the complete clock tree for SoCFPGA Stratix10 chip. The clocking on
Stratix10 is similar to the SoCFPGA Arria10/Cyclone5 platforms, so reuse
the same kind of clock structure as the previous platforms.

Signed-off-by: Dinh Nguyen <***@kernel.org>
---
arch/arm64/boot/dts/altera/socfpga_stratix10.dtsi | 461 +++++++++++++++++++++-
1 file changed, 460 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/altera/socfpga_stratix10.dtsi b/arch/arm64/boot/dts/altera/socfpga_stratix10.dtsi
index a0aa26e..1b035ed 100644
--- a/arch/arm64/boot/dts/altera/socfpga_stratix10.dtsi
+++ b/arch/arm64/boot/dts/altera/socfpga_stratix10.dtsi
@@ -89,9 +89,468 @@
interrupt-parent = <&intc>;
ranges = <0 0 0 0xffffffff>;

- ***@ffd1000 {
+ ***@ffd10000 {
compatible = "altr,clk-mgr";
reg = <0xffd10000 0x1000>;
+
+ clocks {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cb_intosc_hs_div2_clk: cb_intosc_hs_div2_clk {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ };
+
+ cb_intosc_ls_clk: cb_intosc_ls_clk {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ };
+
+ f2s_free_clk: f2s_free_clk {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ };
+
+ osc1: osc1 {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ };
+
+ main_pll: ***@44 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-pll-clock";
+ clocks = <&osc1>, <&cb_intosc_hs_div2_clk>,
+ <&f2s_free_clk>;
+ reg = <0x74>;
+
+ main_mpu_base_clk: main_mpu_base_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>;
+ div-reg = <0x84 0 8>;
+ };
+
+ main_noc_base_clk: main_noc_base_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>;
+ div-reg = <0x88 0 8>;
+ };
+
+ main_emaca_clk: ***@68 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>;
+ reg = <0x50>;
+ };
+
+ main_emacb_clk: ***@6c {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>;
+ reg = <0x54>;
+ };
+
+ main_emac_ptp_clk: ***@70 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>;
+ reg = <0x58>;
+ };
+
+ main_gpio_db_clk: ***@74 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>;
+ reg = <0x5c>;
+ };
+
+ main_sdmmc_clk: ***@78 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk"
+;
+ clocks = <&main_pll>;
+ reg = <0x60>;
+ };
+
+ main_s2f_usr0_clk: ***@7c {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>;
+ reg = <0x64>;
+ };
+
+ main_s2f_usr1_clk: ***@80 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>;
+ reg = <0x68>;
+ };
+
+ main_psi_ref_clk: ***@84 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>;
+ reg = <0x6c>;
+ };
+ };
+
+ periph_pll: ***@e4 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-pll-clock";
+ clocks = <&osc1>, <&cb_intosc_hs_div2_clk>,
+ <&f2s_free_clk>;
+ reg = <0xe4>;
+
+ peri_mpu_base_clk: peri_mpu_base_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&periph_pll>;
+ reg = <0xf4 0 8>;
+ };
+
+ peri_noc_base_clk: peri_noc_base_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&periph_pll>;
+ reg = <0xf8 0 8>;
+ };
+
+ peri_emaca_clk: ***@e8 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>, <&periph_pll>,
+ <&osc1>, <&cb_intosc_hs_div2_clk>, <&f2s_free_clk>;
+ reg = <0xbc>;
+ };
+
+ peri_emacb_clk: ***@ec {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>, <&periph_pll>,
+ <&osc1>, <&cb_intosc_hs_div2_clk>, <&f2s_free_clk>;
+ reg = <0xc0>;
+ };
+
+ peri_emac_ptp_clk: ***@f0 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>, <&periph_pll>,
+ <&osc1>, <&cb_intosc_hs_div2_clk>, <&f2s_free_clk>;
+ reg = <0xc4>;
+ };
+
+ peri_gpio_db_clk: ***@f4 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>, <&periph_pll>,
+ <&osc1>, <&cb_intosc_hs_div2_clk>, <&f2s_free_clk>;
+ reg = <0xc8>;
+ };
+
+ peri_sdmmc_clk: ***@f8 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>, <&periph_pll>,
+ <&osc1>, <&cb_intosc_hs_div2_clk>, <&f2s_free_clk>;
+ reg = <0xcc>;
+ };
+
+ peri_s2f_usr0_clk: ***@fc {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&periph_pll>;
+ reg = <0xd0>;
+ };
+
+ peri_s2f_usr1_clk: ***@100 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>, <&periph_pll>,
+ <&osc1>, <&cb_intosc_hs_div2_clk>, <&f2s_free_clk>;
+ reg = <0xd4>;
+ };
+
+ peri_psi_ref_clk: ***@104 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_pll>, <&periph_pll>,
+ <&osc1>, <&cb_intosc_hs_div2_clk>, <&f2s_free_clk>;
+ reg = <0xd8>;
+ };
+ };
+
+ boot_clk: ***@0 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&osc1>, <&cb_intosc_hs_div2_clk>;
+ reg = <0x0>;
+ };
+
+ mpu_free_clk: ***@48 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_mpu_base_clk>, <&peri_mpu_base_clk>,
+ <&osc1>, <&cb_intosc_hs_div2_clk>,
+ <&f2s_free_clk>;
+ reg = <0x48>;
+ };
+
+ noc_free_clk: ***@4c {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_noc_base_clk>, <&peri_noc_base_clk>,
+ <&osc1>, <&cb_intosc_hs_div2_clk>,
+ <&f2s_free_clk>;
+ reg = <0x4c>;
+ };
+
+ s2f_user0_free_clk: ***@104 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&main_s2f_usr0_clk>, <&peri_s2f_usr0_clk>,
+ <&osc1>, <&cb_intosc_hs_div2_clk>,
+ <&f2s_free_clk>;
+ reg = <0x64>;
+ };
+
+ l4_sys_free_clk: l4_sys_free_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&noc_free_clk>;
+ fixed-divider = <4>;
+ };
+
+ noc_clk: ***@30 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&boot_clk>, <&noc_free_clk>;
+ bypass-reg = <0x3c 1>;
+ };
+
+ emaca_free_clk: emaca_free_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&boot_clk>, <&main_emaca_clk>;
+ bypass-reg = <0xb0 0>;
+ };
+
+ emacb_free_clk: emacb_free_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&boot_clk>, <&main_emacb_clk>;
+ bypass-reg = <0xb0 1>;
+ };
+
+ emac_ptp_free_clk: emac_ptp_free_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&boot_clk>, <&peri_emac_ptp_clk>;
+ bypass-reg = <0xb0 2>;
+ };
+
+ gpio_db_free_clk: gpio_db_free_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&boot_clk>, <&peri_gpio_db_clk>;
+ bypass-reg = <0xb0 3>;
+ };
+
+ sdmmc_free_clk: sdmmc_free_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&boot_clk>, <&peri_sdmmc_clk>;
+ bypass-reg = <0xb0 4>;
+ };
+
+ s2f_user1_free_clk: s2f_user1_free_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&boot_clk>, <&peri_s2f_usr1_clk>;
+ bypass-reg = <0xb0 5>;
+ };
+
+ psi_ref_free_clk: psi_ref_free_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-perip-clk";
+ clocks = <&boot_clk>, <&peri_psi_ref_clk>;
+ bypass-reg = <0xb0 6>;
+ };
+
+ mpu_clk: ***@30 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&boot_clk>, <&mpu_free_clk>;
+ bypass-reg = <0x3c 0>;
+ clk-gate = <0x30 0>;
+ };
+
+ mpu_periph_clk: ***@30 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&mpu_clk>;
+ fixed-divider = <4>;
+ clk-gate = <0x30 0>;
+ };
+
+ mpu_l2ram_clk: ***@30 {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&mpu_clk>;
+ fixed-divider = <2>;
+ clk-gate = <0x30 0>;
+ };
+
+ l4_main_clk: l4_main_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&noc_clk>;
+ div-reg = <0x70 0 2>;
+ clk-gate = <0x30 1>;
+ };
+
+ l4_mp_clk: l4_mp_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&noc_clk>;
+ div-reg= <0x70 8 2>;
+ clk-gate = <0x30 2>;
+ };
+
+ l4_sp_clk: l4_sp_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&noc_clk>;
+ div-reg= <0x70 16 2>;
+ clk-gate = <0x30 3>;
+ };
+
+ cs_at_clk: cs_at_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&noc_clk>;
+ div-reg= <0x70 24 2>;
+ clk-gate = <0x30 4>;
+ };
+
+ cs_trace_clk: cs_trace_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&noc_clk>;
+ div-reg= <0x70 26 2>;
+ clk-gate = <0x30 4>;
+ };
+
+ cs_pdbg_clk: cs_pdbg_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&cs_at_clk>;
+ div-reg= <0x70 28 1>;
+ clk-gate = <0x30 4>;
+ };
+
+ cs_timer_clk: cs_timer_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&noc_clk>;
+ clk-gate = <0x30 5>;
+ };
+
+ s2f_user0_clk: ***@3c {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&boot_clk>, <&f2s_free_clk>;
+ bypass-reg = <0x3c 2>;
+ clk-gate = <0x30 6>;
+ };
+
+ emac0_clk: emac0_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&emaca_free_clk>, <&emacb_free_clk>;
+ bypass-reg = <0xdc 26>;
+ clk-gate = <0xa4 0>;
+ };
+
+ emac1_clk: emac1_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&emaca_free_clk>, <&emacb_free_clk>;
+ bypass-reg = <0xdc 27>;
+ clk-gate = <0xa4 1>;
+ };
+
+ emac2_clk: emac2_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&emaca_free_clk>, <&emacb_free_clk>;
+ bypass-reg = <0xdc 28>;
+ clk-gate = <0xa4 2>;
+ };
+
+ emac_ptp_clk: emac_ptp_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&emac_ptp_free_clk>;
+ clk-gate = <0xa4 3>;
+ };
+
+ gpio_db_clk: gpio_db_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&gpio_db_free_clk>;
+ div-reg = <0xe0>;
+ clk-gate = <0xa4 4>;
+ };
+
+ sdmmc_clk: sdmmc_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&sdmmc_free_clk>;
+ fixed-divider = <4>;
+ clk-gate = <0xa4 5>;
+ };
+
+ s2f_usr1_clk: s2f_usr1_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&s2f_user1_free_clk>;
+ clk-gate = <0xa4 6>;
+ };
+
+ psi_ref_clk: psi_ref_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&psi_ref_free_clk>;
+ clk-gate = <0xa4 7>;
+ };
+
+ usb_clk: usb_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&l4_mp_clk>;
+ clk-gate = <0xa4 8>;
+ };
+
+ spi_m_clk: spi_m_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&l4_mp_clk>;
+ clk-gate = <0xa4 9>;
+ };
+
+ nand_clk: nand_clk {
+ #clock-cells = <0>;
+ compatible = "altr,socfpga-s10-gate-clk";
+ clocks = <&l4_main_clk>;
+ clk-gate = <0xa4 10>;
+ };
+ };
};

gmac0: ***@ff800000 {
--
2.7.4
Dinh Nguyen
2017-07-07 19:03:19 UTC
Permalink
Add a clock driver the SoCFPGA Stratix10 platform. The Stratix10 SOC is an
ARM64 base and the clock tree is very similar to the SOCFPGA ARM32 platforms.

Signed-off-by: Dinh Nguyen <***@kernel.org>
---
drivers/clk/Makefile | 1 +
drivers/clk/socfpga/Makefile | 4 +
drivers/clk/socfpga/clk-gate-s10.c | 214 +++++++++++++++++++++++++++++++++++
drivers/clk/socfpga/clk-periph-s10.c | 165 +++++++++++++++++++++++++++
drivers/clk/socfpga/clk-pll-s10.c | 146 ++++++++++++++++++++++++
drivers/clk/socfpga/clk.c | 9 ++
drivers/clk/socfpga/clk.h | 8 ++
7 files changed, 547 insertions(+)
create mode 100644 drivers/clk/socfpga/clk-gate-s10.c
create mode 100644 drivers/clk/socfpga/clk-periph-s10.c
create mode 100644 drivers/clk/socfpga/clk-pll-s10.c

diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index c19983a..1ce3b10 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -80,6 +80,7 @@ obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
obj-$(CONFIG_COMMON_CLK_SAMSUNG) += samsung/
obj-$(CONFIG_ARCH_SIRF) += sirf/
obj-$(CONFIG_ARCH_SOCFPGA) += socfpga/
+obj-$(CONFIG_ARCH_STRATIX10) += socfpga/
obj-$(CONFIG_PLAT_SPEAR) += spear/
obj-$(CONFIG_ARCH_STI) += st/
obj-$(CONFIG_ARCH_SUNXI) += sunxi/
diff --git a/drivers/clk/socfpga/Makefile b/drivers/clk/socfpga/Makefile
index d8bb239..602e2b9 100644
--- a/drivers/clk/socfpga/Makefile
+++ b/drivers/clk/socfpga/Makefile
@@ -1,5 +1,9 @@
obj-y += clk.o
+ifeq ($(CONFIG_ARCH_SOCFPGA),y)
obj-y += clk-gate.o
obj-y += clk-pll.o
obj-y += clk-periph.o
obj-y += clk-pll-a10.o clk-periph-a10.o clk-gate-a10.o
+else
+obj-y += clk-pll-s10.o clk-periph-s10.o clk-gate-s10.o
+endif
diff --git a/drivers/clk/socfpga/clk-gate-s10.c b/drivers/clk/socfpga/clk-gate-s10.c
new file mode 100644
index 0000000..172317f
--- /dev/null
+++ b/drivers/clk/socfpga/clk-gate-s10.c
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2017, Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/slab.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#include "clk.h"
+
+#define streq(a, b) (strcmp((a), (b)) == 0)
+
+#define SOCFPGA_CS_PDBG_CLK "cs_pdbg_clk"
+
+#define to_socfpga_gate_clk(p) container_of(p, struct socfpga_gate_clk, hw.hw)
+
+/* SDMMC Group for System Manager defines */
+#define SYSMGR_SDMMCGRP_CTRL_OFFSET 0x28
+
+static unsigned long socfpga_gate_clk_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct socfpga_gate_clk *socfpgaclk = to_socfpga_gate_clk(hwclk);
+ u32 div = 1, val;
+
+ if (socfpgaclk->fixed_div)
+ div = socfpgaclk->fixed_div;
+ else if (socfpgaclk->div_reg) {
+ val = readl(socfpgaclk->div_reg) >> socfpgaclk->shift;
+ val &= GENMASK(socfpgaclk->width - 1, 0);
+ div = (1 << val);
+ if (streq(hwclk->init->name, SOCFPGA_CS_PDBG_CLK))
+ div = div ? 4 : 1;
+ }
+ return parent_rate / div;
+}
+
+static int socfpga_clk_prepare(struct clk_hw *hwclk)
+{
+ struct socfpga_gate_clk *socfpgaclk = to_socfpga_gate_clk(hwclk);
+ int i;
+ u32 hs_timing;
+ u32 clk_phase[2];
+
+ if (socfpgaclk->clk_phase[0] || socfpgaclk->clk_phase[1]) {
+ for (i = 0; i < ARRAY_SIZE(clk_phase); i++) {
+ switch (socfpgaclk->clk_phase[i]) {
+ case 0:
+ clk_phase[i] = 0;
+ break;
+ case 45:
+ clk_phase[i] = 1;
+ break;
+ case 90:
+ clk_phase[i] = 2;
+ break;
+ case 135:
+ clk_phase[i] = 3;
+ break;
+ case 180:
+ clk_phase[i] = 4;
+ break;
+ case 225:
+ clk_phase[i] = 5;
+ break;
+ case 270:
+ clk_phase[i] = 6;
+ break;
+ case 315:
+ clk_phase[i] = 7;
+ break;
+ default:
+ clk_phase[i] = 0;
+ break;
+ }
+ }
+
+ hs_timing = SYSMGR_SDMMC_CTRL_SET(clk_phase[0], clk_phase[1]);
+ if (!IS_ERR(socfpgaclk->sys_mgr_base_addr))
+ regmap_write(socfpgaclk->sys_mgr_base_addr,
+ SYSMGR_SDMMCGRP_CTRL_OFFSET, hs_timing);
+ else
+ pr_err("%s: cannot set clk_phase because sys_mgr_base_addr is not available!\n",
+ __func__);
+ }
+ return 0;
+}
+
+static u8 socfpga_gate_get_parent(struct clk_hw *hwclk)
+{
+ struct socfpga_gate_clk *socfpgaclk = to_socfpga_gate_clk(hwclk);
+ u32 mask;
+ u8 parent = 0;
+
+ if (socfpgaclk->bypass_reg) {
+ mask = (0x1 << socfpgaclk->bypass_shift);
+ parent = ((readl(socfpgaclk->bypass_reg) & mask) >>
+ socfpgaclk->bypass_shift);
+ }
+ return parent;
+}
+
+static struct clk_ops gateclk_ops = {
+ .prepare = socfpga_clk_prepare,
+ .recalc_rate = socfpga_gate_clk_recalc_rate,
+ .get_parent = socfpga_gate_get_parent,
+};
+
+static void __init __socfpga_gate_init(struct device_node *node,
+ const struct clk_ops *ops)
+{
+ u32 clk_gate[2];
+ u32 div_reg[3];
+ u32 bypass_reg[2];
+ u32 clk_phase[2];
+ u32 fixed_div;
+ struct clk *clk;
+ struct socfpga_gate_clk *socfpga_clk;
+ const char *clk_name = node->name;
+ const char *parent_name[SOCFPGA_MAX_PARENTS];
+ struct clk_init_data init;
+ int rc;
+
+ socfpga_clk = kzalloc(sizeof(*socfpga_clk), GFP_KERNEL);
+ if (WARN_ON(!socfpga_clk))
+ return;
+
+ rc = of_property_read_u32_array(node, "clk-gate", clk_gate, 2);
+ if (rc)
+ clk_gate[0] = 0;
+
+ if (clk_gate[0]) {
+ socfpga_clk->hw.reg = clk_mgr_s10_base_addr + clk_gate[0];
+ socfpga_clk->hw.bit_idx = clk_gate[1];
+
+ gateclk_ops.enable = clk_gate_ops.enable;
+ gateclk_ops.disable = clk_gate_ops.disable;
+ }
+
+ rc = of_property_read_u32(node, "fixed-divider", &fixed_div);
+ if (rc)
+ socfpga_clk->fixed_div = 0;
+ else
+ socfpga_clk->fixed_div = fixed_div;
+
+ rc = of_property_read_u32_array(node, "div-reg", div_reg, 3);
+ if (!rc) {
+ socfpga_clk->div_reg = clk_mgr_s10_base_addr + div_reg[0];
+ socfpga_clk->shift = div_reg[1];
+ socfpga_clk->width = div_reg[2];
+ } else {
+ socfpga_clk->div_reg = NULL;
+ }
+
+ rc = of_property_read_u32_array(node, "bypass-reg", bypass_reg, 2);
+ if (!rc) {
+ socfpga_clk->bypass_reg = clk_mgr_s10_base_addr + bypass_reg[0];
+ socfpga_clk->bypass_shift = bypass_reg[1];
+ } else {
+ socfpga_clk->bypass_reg = NULL;
+ }
+
+ rc = of_property_read_u32_array(node, "clk-phase", clk_phase, 2);
+ if (!rc) {
+ socfpga_clk->clk_phase[0] = clk_phase[0];
+ socfpga_clk->clk_phase[1] = clk_phase[1];
+
+ socfpga_clk->sys_mgr_base_addr =
+ syscon_regmap_lookup_by_compatible("altr,sys-mgr");
+ if (IS_ERR(socfpga_clk->sys_mgr_base_addr)) {
+ pr_err("%s: failed to find altr,sys-mgr regmap!\n",
+ __func__);
+ return;
+ }
+ }
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+
+ init.name = clk_name;
+ init.ops = ops;
+ init.flags = 0;
+
+ init.num_parents = of_clk_parent_fill(node, parent_name, SOCFPGA_MAX_PARENTS);
+ init.parent_names = parent_name;
+ socfpga_clk->hw.hw.init = &init;
+
+ clk = clk_register(NULL, &socfpga_clk->hw.hw);
+ if (WARN_ON(IS_ERR(clk))) {
+ kfree(socfpga_clk);
+ return;
+ }
+ rc = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ if (WARN_ON(rc))
+ return;
+}
+
+void __init socfpga_s10_gate_init(struct device_node *node)
+{
+ __socfpga_gate_init(node, &gateclk_ops);
+}
diff --git a/drivers/clk/socfpga/clk-periph-s10.c b/drivers/clk/socfpga/clk-periph-s10.c
new file mode 100644
index 0000000..2180d36
--- /dev/null
+++ b/drivers/clk/socfpga/clk-periph-s10.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2017, Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/slab.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+
+#include "clk.h"
+
+#define CLK_MGR_FREE_SHIFT 16
+#define CLK_MGR_FREE_MASK 0x7
+
+#define SWCTRLBTCLKSEL_MASK 0x200
+#define SWCTRLBTCLKSEL_SHIFT 9
+#define SWCTRLBTCLKEN_MASK 0x100
+#define SWCTRLBTCLKSEN_SHIFT 8
+
+
+#define SOCFPGA_BOOT_CLK "boot_clk"
+
+#define to_socfpga_periph_clk(p) container_of(p, struct socfpga_periph_clk, hw.hw)
+
+static unsigned long clk_periclk_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct socfpga_periph_clk *socfpgaclk = to_socfpga_periph_clk(hwclk);
+ u32 div = 1;
+ u32 val;
+
+ if (socfpgaclk->fixed_div) {
+ div = socfpgaclk->fixed_div;
+ } else if (socfpgaclk->div_reg) {
+ val = readl(socfpgaclk->div_reg) >> socfpgaclk->shift;
+ val &= GENMASK(socfpgaclk->width - 1, 0);
+ parent_rate /= val;
+ } else {
+ if (streq(hwclk->init->name, SOCFPGA_BOOT_CLK)) {
+ if ((readl(socfpgaclk->hw.reg) & SWCTRLBTCLKEN_MASK) >>
+ SWCTRLBTCLKSEN_SHIFT) {
+ div = ((readl(socfpgaclk->hw.reg) &
+ SWCTRLBTCLKSEL_MASK) >>
+ SWCTRLBTCLKSEL_SHIFT);
+ div += 1;
+ }
+ } else if (!socfpgaclk->bypass_reg)
+ div = ((readl(socfpgaclk->hw.reg) & 0x7ff) + 1);
+ }
+
+ return parent_rate / div;
+}
+
+static u8 clk_periclk_get_parent(struct clk_hw *hwclk)
+{
+ struct socfpga_periph_clk *socfpgaclk = to_socfpga_periph_clk(hwclk);
+ u32 clk_src, mask;
+ u8 parent;
+
+ if (socfpgaclk->bypass_reg) {
+ mask = (0x1 << socfpgaclk->bypass_shift);
+ parent = ((readl(socfpgaclk->bypass_reg) & mask) >>
+ socfpgaclk->bypass_shift);
+ } else {
+ clk_src = readl(socfpgaclk->hw.reg);
+ parent = (clk_src >> CLK_MGR_FREE_SHIFT) &
+ CLK_MGR_FREE_MASK;
+ }
+ return parent;
+}
+
+static const struct clk_ops periclk_ops = {
+ .recalc_rate = clk_periclk_recalc_rate,
+ .get_parent = clk_periclk_get_parent,
+};
+
+static __init void __socfpga_periph_init(struct device_node *node,
+ const struct clk_ops *ops)
+{
+ u32 reg;
+ struct clk *clk;
+ struct socfpga_periph_clk *periph_clk;
+ const char *clk_name = node->name;
+ const char *parent_name[SOCFPGA_MAX_PARENTS];
+ struct clk_init_data init;
+ int rc;
+ u32 fixed_div;
+ u32 div_reg[3];
+ u32 bypass_reg[2];
+
+ of_property_read_u32(node, "reg", &reg);
+
+ periph_clk = kzalloc(sizeof(*periph_clk), GFP_KERNEL);
+ if (WARN_ON(!periph_clk))
+ return;
+
+ periph_clk->hw.reg = clk_mgr_s10_base_addr + reg;
+
+ rc = of_property_read_u32_array(node, "div-reg", div_reg, 3);
+ if (!rc) {
+ periph_clk->div_reg = clk_mgr_s10_base_addr + div_reg[0];
+ periph_clk->shift = div_reg[1];
+ periph_clk->width = div_reg[2];
+ } else {
+ periph_clk->div_reg = NULL;
+ }
+
+ rc = of_property_read_u32_array(node, "bypass-reg", bypass_reg, 2);
+ if (!rc) {
+ periph_clk->bypass_reg = clk_mgr_s10_base_addr + bypass_reg[0];
+ periph_clk->bypass_shift = bypass_reg[1];
+ } else {
+ periph_clk->bypass_reg = NULL;
+ }
+
+ rc = of_property_read_u32(node, "fixed-divider", &fixed_div);
+ if (rc)
+ periph_clk->fixed_div = 0;
+ else
+ periph_clk->fixed_div = fixed_div;
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+
+ init.name = clk_name;
+ init.ops = ops;
+ init.flags = 0;
+
+ init.num_parents = of_clk_parent_fill(node, parent_name, SOCFPGA_MAX_PARENTS);
+ init.parent_names = parent_name;
+
+ periph_clk->hw.hw.init = &init;
+
+ clk = clk_register(NULL, &periph_clk->hw.hw);
+ if (WARN_ON(IS_ERR(clk))) {
+ kfree(periph_clk);
+ return;
+ }
+ rc = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ if (rc < 0) {
+ pr_err("Could not register clock provider for node:%s\n",
+ clk_name);
+ goto err_clk;
+ }
+
+ return;
+
+err_clk:
+ clk_unregister(clk);
+}
+
+void __init socfpga_s10_periph_init(struct device_node *node)
+{
+ __socfpga_periph_init(node, &periclk_ops);
+}
diff --git a/drivers/clk/socfpga/clk-pll-s10.c b/drivers/clk/socfpga/clk-pll-s10.c
new file mode 100644
index 0000000..3f36ad5
--- /dev/null
+++ b/drivers/clk/socfpga/clk-pll-s10.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017, Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/slab.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include "clk.h"
+
+/* Clock Manager offsets */
+#define CLK_MGR_PLL_CLK_SRC_SHIFT 16
+#define CLK_MGR_PLL_CLK_SRC_MASK 0x3
+
+/* PLL Clock enable bits */
+#define SOCFPGA_PLL_POWER 0
+#define SOCFPGA_PLL_RESET_MASK 0x2
+#define SOCFPGA_PLL_REFDIV_MASK 0x00003F00
+#define SOCFPGA_PLL_REFDIV_SHIFT 8
+#define SOCFPGA_PLL_MDIV_MASK 0xFF000000
+#define SOCFPGA_PLL_MDIV_SHIFT 24
+#define SOCFGPA_MAX_PARENTS 5
+
+#define to_socfpga_clk(p) container_of(p, struct socfpga_pll, hw.hw)
+
+void __iomem *clk_mgr_s10_base_addr;
+
+static unsigned long clk_pll_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct socfpga_pll *socfpgaclk = to_socfpga_clk(hwclk);
+ unsigned long mdiv;
+ unsigned long refdiv;
+ unsigned long reg;
+ unsigned long long vco_freq;
+
+ /* read VCO1 reg for numerator and denominator */
+ reg = readl(socfpgaclk->hw.reg);
+ refdiv = (reg & SOCFPGA_PLL_REFDIV_MASK) >> SOCFPGA_PLL_REFDIV_SHIFT;
+ vco_freq = (unsigned long long)parent_rate/refdiv;
+
+ /* Read mdiv and fdiv from the fdbck register */
+ reg = readl(socfpgaclk->hw.reg + 0x4);
+ mdiv = (reg & SOCFPGA_PLL_MDIV_MASK) >> SOCFPGA_PLL_MDIV_SHIFT;
+ vco_freq = (unsigned long long)parent_rate * (mdiv + 6);
+
+ return (unsigned long)vco_freq;
+}
+
+static u8 clk_pll_get_parent(struct clk_hw *hwclk)
+{
+ struct socfpga_pll *socfpgaclk = to_socfpga_clk(hwclk);
+ u32 pll_src;
+
+ pll_src = readl(socfpgaclk->hw.reg);
+
+ return (pll_src >> CLK_MGR_PLL_CLK_SRC_SHIFT) &
+ CLK_MGR_PLL_CLK_SRC_MASK;
+}
+
+static int clk_pll_prepare(struct clk_hw *hwclk)
+{
+ struct socfpga_pll *socfpgaclk = to_socfpga_clk(hwclk);
+ u32 reg;
+
+ /* Bring PLL out of reset */
+ reg = readl(socfpgaclk->hw.reg);
+ reg |= SOCFPGA_PLL_RESET_MASK;
+ writel(reg, socfpgaclk->hw.reg);
+
+ return 0;
+}
+
+static struct clk_ops clk_pll_ops = {
+ .recalc_rate = clk_pll_recalc_rate,
+ .get_parent = clk_pll_get_parent,
+ .prepare = clk_pll_prepare,
+};
+
+static struct clk * __init __socfpga_pll_init(struct device_node *node,
+ const struct clk_ops *ops)
+{
+ u32 reg, reg2;
+ struct clk *clk;
+ struct socfpga_pll *pll_clk;
+ const char *clk_name = node->name;
+ const char *parent_name[SOCFGPA_MAX_PARENTS];
+ struct clk_init_data init;
+ struct device_node *clkmgr_np;
+ int rc;
+ int i = 0;
+
+ of_property_read_u32(node, "reg", &reg);
+
+ pll_clk = kzalloc(sizeof(*pll_clk), GFP_KERNEL);
+ if (WARN_ON(!pll_clk))
+ return NULL;
+
+ clkmgr_np = of_find_compatible_node(NULL, NULL, "altr,clk-mgr");
+ clk_mgr_s10_base_addr = of_iomap(clkmgr_np, 0);
+ BUG_ON(!clk_mgr_s10_base_addr);
+ pll_clk->hw.reg = clk_mgr_s10_base_addr + reg;
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+
+ init.name = clk_name;
+ init.ops = ops;
+ init.flags = 0;
+
+ while (i < SOCFGPA_MAX_PARENTS && (parent_name[i] =
+ of_clk_get_parent_name(node, i)) != NULL)
+ i++;
+ init.num_parents = i;
+ init.parent_names = parent_name;
+ pll_clk->hw.hw.init = &init;
+
+ pll_clk->hw.bit_idx = SOCFPGA_PLL_POWER;
+ clk_pll_ops.enable = clk_gate_ops.enable;
+ clk_pll_ops.disable = clk_gate_ops.disable;
+
+ clk = clk_register(NULL, &pll_clk->hw.hw);
+ if (WARN_ON(IS_ERR(clk))) {
+ kfree(pll_clk);
+ return NULL;
+ }
+ rc = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ return clk;
+}
+
+void __init socfpga_s10_pll_init(struct device_node *node)
+{
+ __socfpga_pll_init(node, &clk_pll_ops);
+}
diff --git a/drivers/clk/socfpga/clk.c b/drivers/clk/socfpga/clk.c
index 7564d2e..abb374b 100644
--- a/drivers/clk/socfpga/clk.c
+++ b/drivers/clk/socfpga/clk.c
@@ -21,6 +21,7 @@

#include "clk.h"

+#ifdef CONFIG_ARCH_SOCFPGA
CLK_OF_DECLARE(socfpga_pll_clk, "altr,socfpga-pll-clock", socfpga_pll_init);
CLK_OF_DECLARE(socfpga_perip_clk, "altr,socfpga-perip-clk", socfpga_periph_init);
CLK_OF_DECLARE(socfpga_gate_clk, "altr,socfpga-gate-clk", socfpga_gate_init);
@@ -30,3 +31,11 @@ CLK_OF_DECLARE(socfpga_a10_perip_clk, "altr,socfpga-a10-perip-clk",
socfpga_a10_periph_init);
CLK_OF_DECLARE(socfpga_a10_gate_clk, "altr,socfpga-a10-gate-clk",
socfpga_a10_gate_init);
+#elif CONFIG_ARCH_STRATIX10
+CLK_OF_DECLARE(socfpga_s10_pll_clk, "altr,socfpga-s10-pll-clock",
+ socfpga_s10_pll_init);
+CLK_OF_DECLARE(socfpga_s10_perip_clk, "altr,socfpga-s10-perip-clk",
+ socfpga_s10_periph_init);
+CLK_OF_DECLARE(socfpga_s10_gate_clk, "altr,socfpga-s10-gate-clk",
+ socfpga_s10_gate_init);
+#endif
diff --git a/drivers/clk/socfpga/clk.h b/drivers/clk/socfpga/clk.h
index 814c724..7eeb140 100644
--- a/drivers/clk/socfpga/clk.h
+++ b/drivers/clk/socfpga/clk.h
@@ -34,6 +34,7 @@

extern void __iomem *clk_mgr_base_addr;
extern void __iomem *clk_mgr_a10_base_addr;
+extern void __iomem *clk_mgr_s10_base_addr;

void __init socfpga_pll_init(struct device_node *node);
void __init socfpga_periph_init(struct device_node *node);
@@ -41,6 +42,9 @@ void __init socfpga_gate_init(struct device_node *node);
void socfpga_a10_pll_init(struct device_node *node);
void socfpga_a10_periph_init(struct device_node *node);
void socfpga_a10_gate_init(struct device_node *node);
+void socfpga_s10_pll_init(struct device_node *node);
+void socfpga_s10_periph_init(struct device_node *node);
+void socfpga_s10_gate_init(struct device_node *node);

struct socfpga_pll {
struct clk_gate hw;
@@ -51,9 +55,11 @@ struct socfpga_gate_clk {
char *parent_name;
u32 fixed_div;
void __iomem *div_reg;
+ void __iomem *bypass_reg;
struct regmap *sys_mgr_base_addr;
u32 width; /* only valid if div_reg != 0 */
u32 shift; /* only valid if div_reg != 0 */
+ u32 bypass_shift; /* only valid if bypass_reg != 0 */
u32 clk_phase[2];
};

@@ -62,8 +68,10 @@ struct socfpga_periph_clk {
char *parent_name;
u32 fixed_div;
void __iomem *div_reg;
+ void __iomem *bypass_reg;
u32 width; /* only valid if div_reg != 0 */
u32 shift; /* only valid if div_reg != 0 */
+ u32 bypass_shift; /* only valid if bypass_reg != 0 */
};

#endif /* SOCFPGA_CLK_H */
--
2.7.4
Rob Herring
2017-07-10 15:51:34 UTC
Permalink
Update the bindings document for the Arria10 and Stratix10 clock bindings.
---
Documentation/devicetree/bindings/clock/altr_socfpga.txt | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/altr_socfpga.txt b/Documentation/devicetree/bindings/clock/altr_socfpga.txt
index f72e80e..1c32658 100644
--- a/Documentation/devicetree/bindings/clock/altr_socfpga.txt
+++ b/Documentation/devicetree/bindings/clock/altr_socfpga.txt
"altr,socfpga-gate-clk" - Clocks that directly feed peripherals and
can get gated.
+ "altr,socfpga-a10-pll-clock" - for a PLL clock
+ "altr,socfpga-a10-perip-clock" - The peripheral clock divided from the
+ PLL clock.
+ "altr,socfpga-a10-gate-clk" - Clocks that directly feed peripherals and
+ can get gated.
+
+ "altr,socfpga-s10-pll-clock" - for a PLL clock
+ "altr,socfpga-s10-perip-clock" - The peripheral clock divided from the
+ PLL clock.
+ "altr,socfpga-s10-gate-clk" - Clocks that directly feed peripherals and
+ can get gated.
We're generally not doing a clock per node clock providers on new
platforms and doing a single (or few) clock controller nodes instead. It
doesn't look like there's much or any reuse here from older platforms
which would be the main reason to keep this style.

Rob
Stephen Boyd
2017-07-21 20:38:47 UTC
Permalink
Post by Rob Herring
Update the bindings document for the Arria10 and Stratix10 clock bindings.
---
Documentation/devicetree/bindings/clock/altr_socfpga.txt | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/altr_socfpga.txt b/Documentation/devicetree/bindings/clock/altr_socfpga.txt
index f72e80e..1c32658 100644
--- a/Documentation/devicetree/bindings/clock/altr_socfpga.txt
+++ b/Documentation/devicetree/bindings/clock/altr_socfpga.txt
"altr,socfpga-gate-clk" - Clocks that directly feed peripherals and
can get gated.
+ "altr,socfpga-a10-pll-clock" - for a PLL clock
+ "altr,socfpga-a10-perip-clock" - The peripheral clock divided from the
+ PLL clock.
+ "altr,socfpga-a10-gate-clk" - Clocks that directly feed peripherals and
+ can get gated.
+
+ "altr,socfpga-s10-pll-clock" - for a PLL clock
+ "altr,socfpga-s10-perip-clock" - The peripheral clock divided from the
+ PLL clock.
+ "altr,socfpga-s10-gate-clk" - Clocks that directly feed peripherals and
+ can get gated.
We're generally not doing a clock per node clock providers on new
platforms and doing a single (or few) clock controller nodes instead. It
doesn't look like there's much or any reuse here from older platforms
which would be the main reason to keep this style.
Agreed. Can this be rewritten to new style binding?
--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
Loading...