Commit 2b684c07 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'clk-for-linus-3.13' of git://git.linaro.org/people/mturquette/linux

Pull clock framework changes from Mike Turquette:
 "The clock changes for 3.13 are an even mix of framework improvements &
  bug fixes along with updates to existing clock drivers and the
  additional of new clock drivers"

* tag 'clk-for-linus-3.13' of git://git.linaro.org/people/mturquette/linux:
  clk: new driver for efm32 SoC
  clk: of: helper for determining number of parent clocks
  clk/zynq: Fix possible memory leak
  clk: keystone: Build Keystone clock drivers
  clk: keystone: Add gate control clock driver
  clk: keystone: add Keystone PLL clock driver
  Documentation: Add documentation for APM X-Gene clock binding
  clk: arm64: Add DTS clock entry for APM X-Gene Storm SoC
  clk: Add APM X-Gene SoC clock driver
  clk: wm831x: get rid of the implementation of remove function
  clk: Correct lookup logic in clk_fetch_parent_index()
  clk: Use kcalloc() to allocate arrays
  clk: Add error handling to clk_fetch_parent_index()
parents c2d33069 9ed9c07d
* Clock bindings for Energy Micro efm32 Giant Gecko's Clock Management Unit
Required properties:
- compatible: Should be "efm32gg,cmu"
- reg: Base address and length of the register set
- interrupts: Interrupt used by the CMU
- #clock-cells: Should be <1>
The clock consumer should specify the desired clock by having the clock ID in
its "clocks" phandle cell. The header efm32-clk.h contains a list of available
IDs.
Status: Unstable - ABI compatibility may be broken in the future
Binding for Keystone gate control driver which uses PSC controller IP.
This binding uses the common clock binding[1].
[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
Required properties:
- compatible : shall be "ti,keystone,psc-clock".
- #clock-cells : from common clock binding; shall be set to 0.
- clocks : parent clock phandle
- reg : psc control and domain address address space
- reg-names : psc control and domain registers
- domain-id : psc domain id needed to check the transition state register
Optional properties:
- clock-output-names : From common clock binding to override the
default output clock name
Example:
clkusb: clkusb {
#clock-cells = <0>;
compatible = "ti,keystone,psc-clock";
clocks = <&chipclk16>;
clock-output-names = "usb";
reg = <0x02350008 0xb00>, <0x02350000 0x400>;
reg-names = "control", "domain";
domain-id = <0>;
};
Status: Unstable - ABI compatibility may be broken in the future
Binding for keystone PLLs. The main PLL IP typically has a multiplier,
a divider and a post divider. The additional PLL IPs like ARMPLL, DDRPLL
and PAPLL are controlled by the memory mapped register where as the Main
PLL is controlled by a PLL controller registers along with memory mapped
registers.
This binding uses the common clock binding[1].
[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
Required properties:
- #clock-cells : from common clock binding; shall be set to 0.
- compatible : shall be "ti,keystone,main-pll-clock" or "ti,keystone,pll-clock"
- clocks : parent clock phandle
- reg - pll control0 and pll multipler registers
- reg-names : control and multiplier. The multiplier is applicable only for
main pll clock
- fixed-postdiv : fixed post divider value
Example:
mainpllclk: mainpllclk@2310110 {
#clock-cells = <0>;
compatible = "ti,keystone,main-pll-clock";
clocks = <&refclkmain>;
reg = <0x02620350 4>, <0x02310110 4>;
reg-names = "control", "multiplier";
fixed-postdiv = <2>;
};
papllclk: papllclk@2620358 {
#clock-cells = <0>;
compatible = "ti,keystone,pll-clock";
clocks = <&refclkmain>;
clock-output-names = "pa-pll-clk";
reg = <0x02620358 4>;
reg-names = "control";
fixed-postdiv = <6>;
};
Required properties:
- #clock-cells : from common clock binding; shall be set to 0.
- compatible : shall be "ti,keystone,pll-mux-clock"
- clocks : link phandles of parent clocks
- reg - pll mux register
- bit-shift : number of bits to shift the bit-mask
- bit-mask : arbitrary bitmask for programming the mux
Optional properties:
- clock-output-names : From common clock binding.
Example:
mainmuxclk: mainmuxclk@2310108 {
#clock-cells = <0>;
compatible = "ti,keystone,pll-mux-clock";
clocks = <&mainpllclk>, <&refclkmain>;
reg = <0x02310108 4>;
bit-shift = <23>;
bit-mask = <1>;
clock-output-names = "mainmuxclk";
};
Required properties:
- #clock-cells : from common clock binding; shall be set to 0.
- compatible : shall be "ti,keystone,pll-divider-clock"
- clocks : parent clock phandle
- reg - pll mux register
- bit-shift : number of bits to shift the bit-mask
- bit-mask : arbitrary bitmask for programming the divider
Optional properties:
- clock-output-names : From common clock binding.
Example:
gemtraceclk: gemtraceclk@2310120 {
#clock-cells = <0>;
compatible = "ti,keystone,pll-divider-clock";
clocks = <&mainmuxclk>;
reg = <0x02310120 4>;
bit-shift = <0>;
bit-mask = <8>;
clock-output-names = "gemtraceclk";
};
Device Tree Clock bindings for APM X-Gene
This binding uses the common clock binding[1].
[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
Required properties:
- compatible : shall be one of the following:
"apm,xgene-socpll-clock" - for a X-Gene SoC PLL clock
"apm,xgene-pcppll-clock" - for a X-Gene PCP PLL clock
"apm,xgene-device-clock" - for a X-Gene device clock
Required properties for SoC or PCP PLL clocks:
- reg : shall be the physical PLL register address for the pll clock.
- clocks : shall be the input parent clock phandle for the clock. This should
be the reference clock.
- #clock-cells : shall be set to 1.
- clock-output-names : shall be the name of the PLL referenced by derive
clock.
Optional properties for PLL clocks:
- clock-names : shall be the name of the PLL. If missing, use the device name.
Required properties for device clocks:
- reg : shall be a list of address and length pairs describing the CSR
reset and/or the divider. Either may be omitted, but at least
one must be present.
- reg-names : shall be a string list describing the reg resource. This
may include "csr-reg" and/or "div-reg". If this property
is not present, the reg property is assumed to describe
only "csr-reg".
- clocks : shall be the input parent clock phandle for the clock.
- #clock-cells : shall be set to 1.
- clock-output-names : shall be the name of the device referenced.
Optional properties for device clocks:
- clock-names : shall be the name of the device clock. If missing, use the
device name.
- csr-offset : Offset to the CSR reset register from the reset address base.
Default is 0.
- csr-mask : CSR reset mask bit. Default is 0xF.
- enable-offset : Offset to the enable register from the reset address base.
Default is 0x8.
- enable-mask : CSR enable mask bit. Default is 0xF.
- divider-offset : Offset to the divider CSR register from the divider base.
Default is 0x0.
- divider-width : Width of the divider register. Default is 0.
- divider-shift : Bit shift of the divider register. Default is 0.
For example:
pcppll: pcppll@17000100 {
compatible = "apm,xgene-pcppll-clock";
#clock-cells = <1>;
clocks = <&refclk 0>;
clock-names = "pcppll";
reg = <0x0 0x17000100 0x0 0x1000>;
clock-output-names = "pcppll";
type = <0>;
};
socpll: socpll@17000120 {
compatible = "apm,xgene-socpll-clock";
#clock-cells = <1>;
clocks = <&refclk 0>;
clock-names = "socpll";
reg = <0x0 0x17000120 0x0 0x1000>;
clock-output-names = "socpll";
type = <1>;
};
qmlclk: qmlclk {
compatible = "apm,xgene-device-clock";
#clock-cells = <1>;
clocks = <&socplldiv2 0>;
clock-names = "qmlclk";
reg = <0x0 0x1703C000 0x0 0x1000>;
reg-name = "csr-reg";
clock-output-names = "qmlclk";
};
ethclk: ethclk {
compatible = "apm,xgene-device-clock";
#clock-cells = <1>;
clocks = <&socplldiv2 0>;
clock-names = "ethclk";
reg = <0x0 0x17000000 0x0 0x1000>;
reg-names = "div-reg";
divider-offset = <0x238>;
divider-width = <0x9>;
divider-shift = <0x0>;
clock-output-names = "ethclk";
};
apbclk: apbclk {
compatible = "apm,xgene-device-clock";
#clock-cells = <1>;
clocks = <&ahbclk 0>;
clock-names = "apbclk";
reg = <0x0 0x1F2AC000 0x0 0x1000
0x0 0x1F2AC000 0x0 0x1000>;
reg-names = "csr-reg", "div-reg";
csr-offset = <0x0>;
csr-mask = <0x200>;
enable-offset = <0x8>;
enable-mask = <0x200>;
divider-offset = <0x10>;
divider-width = <0x2>;
divider-shift = <0x0>;
flags = <0x8>;
clock-output-names = "apbclk";
};
...@@ -103,6 +103,81 @@ soc { ...@@ -103,6 +103,81 @@ soc {
#size-cells = <2>; #size-cells = <2>;
ranges; ranges;
clocks {
#address-cells = <2>;
#size-cells = <2>;
ranges;
refclk: refclk {
compatible = "fixed-clock";
#clock-cells = <1>;
clock-frequency = <100000000>;
clock-output-names = "refclk";
};
pcppll: pcppll@17000100 {
compatible = "apm,xgene-pcppll-clock";
#clock-cells = <1>;
clocks = <&refclk 0>;
clock-names = "pcppll";
reg = <0x0 0x17000100 0x0 0x1000>;
clock-output-names = "pcppll";
type = <0>;
};
socpll: socpll@17000120 {
compatible = "apm,xgene-socpll-clock";
#clock-cells = <1>;
clocks = <&refclk 0>;
clock-names = "socpll";
reg = <0x0 0x17000120 0x0 0x1000>;
clock-output-names = "socpll";
type = <1>;
};
socplldiv2: socplldiv2 {
compatible = "fixed-factor-clock";
#clock-cells = <1>;
clocks = <&socpll 0>;
clock-names = "socplldiv2";
clock-mult = <1>;
clock-div = <2>;
clock-output-names = "socplldiv2";
};
qmlclk: qmlclk {
compatible = "apm,xgene-device-clock";
#clock-cells = <1>;
clocks = <&socplldiv2 0>;
clock-names = "qmlclk";
reg = <0x0 0x1703C000 0x0 0x1000>;
reg-names = "csr-reg";
clock-output-names = "qmlclk";
};
ethclk: ethclk {
compatible = "apm,xgene-device-clock";
#clock-cells = <1>;
clocks = <&socplldiv2 0>;
clock-names = "ethclk";
reg = <0x0 0x17000000 0x0 0x1000>;
reg-names = "div-reg";
divider-offset = <0x238>;
divider-width = <0x9>;
divider-shift = <0x0>;
clock-output-names = "ethclk";
};
eth8clk: eth8clk {
compatible = "apm,xgene-device-clock";
#clock-cells = <1>;
clocks = <&ethclk 0>;
clock-names = "eth8clk";
reg = <0x0 0x1702C000 0x0 0x1000>;
reg-names = "csr-reg";
clock-output-names = "eth8clk";
};
};
serial0: serial@1c020000 { serial0: serial@1c020000 {
device_type = "serial"; device_type = "serial";
compatible = "ns16550"; compatible = "ns16550";
......
...@@ -93,6 +93,20 @@ config CLK_PPC_CORENET ...@@ -93,6 +93,20 @@ config CLK_PPC_CORENET
This adds the clock driver support for Freescale PowerPC corenet This adds the clock driver support for Freescale PowerPC corenet
platforms using common clock framework. platforms using common clock framework.
config COMMON_CLK_XGENE
bool "Clock driver for APM XGene SoC"
default y
depends on ARM64
---help---
Sypport for the APM X-Gene SoC reference, PLL, and device clocks.
config COMMON_CLK_KEYSTONE
tristate "Clock drivers for Keystone based SOCs"
depends on ARCH_KEYSTONE && OF
---help---
Supports clock drivers for Keystone based SOCs. These SOCs have local
a power sleep control module that gate the clock to the IPs and PLLs.
endmenu endmenu
source "drivers/clk/mvebu/Kconfig" source "drivers/clk/mvebu/Kconfig"
...@@ -11,6 +11,7 @@ obj-$(CONFIG_COMMON_CLK) += clk-composite.o ...@@ -11,6 +11,7 @@ obj-$(CONFIG_COMMON_CLK) += clk-composite.o
# SoCs specific # SoCs specific
obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835.o obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835.o
obj-$(CONFIG_ARCH_EFM32) += clk-efm32gg.o
obj-$(CONFIG_ARCH_NOMADIK) += clk-nomadik.o obj-$(CONFIG_ARCH_NOMADIK) += clk-nomadik.o
obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o
obj-$(CONFIG_ARCH_NSPIRE) += clk-nspire.o obj-$(CONFIG_ARCH_NSPIRE) += clk-nspire.o
...@@ -32,6 +33,8 @@ obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o ...@@ -32,6 +33,8 @@ obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o
obj-$(CONFIG_ARCH_ZYNQ) += zynq/ obj-$(CONFIG_ARCH_ZYNQ) += zynq/
obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-$(CONFIG_ARCH_TEGRA) += tegra/
obj-$(CONFIG_PLAT_SAMSUNG) += samsung/ obj-$(CONFIG_PLAT_SAMSUNG) += samsung/
obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o
obj-$(CONFIG_COMMON_CLK_KEYSTONE) += keystone/
obj-$(CONFIG_X86) += x86/ obj-$(CONFIG_X86) += x86/
......
/*
* Copyright (C) 2013 Pengutronix
* Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>
*
* 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/clk.h>
#include <linux/io.h>
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <dt-bindings/clock/efm32-cmu.h>
#define CMU_HFPERCLKEN0 0x44
static struct clk *clk[37];
static struct clk_onecell_data clk_data = {
.clks = clk,
.clk_num = ARRAY_SIZE(clk),
};
static int __init efm32gg_cmu_init(struct device_node *np)
{
int i;
void __iomem *base;
for (i = 0; i < ARRAY_SIZE(clk); ++i)
clk[i] = ERR_PTR(-ENOENT);
base = of_iomap(np, 0);
if (!base) {
pr_warn("Failed to map address range for efm32gg,cmu node\n");
return -EADDRNOTAVAIL;
}
clk[clk_HFXO] = clk_register_fixed_rate(NULL, "HFXO", NULL,
CLK_IS_ROOT, 48000000);
clk[clk_HFPERCLKUSART0] = clk_register_gate(NULL, "HFPERCLK.USART0",
"HFXO", 0, base + CMU_HFPERCLKEN0, 0, 0, NULL);
clk[clk_HFPERCLKUSART1] = clk_register_gate(NULL, "HFPERCLK.USART1",
"HFXO", 0, base + CMU_HFPERCLKEN0, 1, 0, NULL);
clk[clk_HFPERCLKUSART2] = clk_register_gate(NULL, "HFPERCLK.USART2",
"HFXO", 0, base + CMU_HFPERCLKEN0, 2, 0, NULL);
clk[clk_HFPERCLKUART0] = clk_register_gate(NULL, "HFPERCLK.UART0",
"HFXO", 0, base + CMU_HFPERCLKEN0, 3, 0, NULL);
clk[clk_HFPERCLKUART1] = clk_register_gate(NULL, "HFPERCLK.UART1",
"HFXO", 0, base + CMU_HFPERCLKEN0, 4, 0, NULL);
clk[clk_HFPERCLKTIMER0] = clk_register_gate(NULL, "HFPERCLK.TIMER0",
"HFXO", 0, base + CMU_HFPERCLKEN0, 5, 0, NULL);
clk[clk_HFPERCLKTIMER1] = clk_register_gate(NULL, "HFPERCLK.TIMER1",
"HFXO", 0, base + CMU_HFPERCLKEN0, 6, 0, NULL);
clk[clk_HFPERCLKTIMER2] = clk_register_gate(NULL, "HFPERCLK.TIMER2",
"HFXO", 0, base + CMU_HFPERCLKEN0, 7, 0, NULL);
clk[clk_HFPERCLKTIMER3] = clk_register_gate(NULL, "HFPERCLK.TIMER3",
"HFXO", 0, base + CMU_HFPERCLKEN0, 8, 0, NULL);
clk[clk_HFPERCLKACMP0] = clk_register_gate(NULL, "HFPERCLK.ACMP0",
"HFXO", 0, base + CMU_HFPERCLKEN0, 9, 0, NULL);
clk[clk_HFPERCLKACMP1] = clk_register_gate(NULL, "HFPERCLK.ACMP1",
"HFXO", 0, base + CMU_HFPERCLKEN0, 10, 0, NULL);
clk[clk_HFPERCLKI2C0] = clk_register_gate(NULL, "HFPERCLK.I2C0",
"HFXO", 0, base + CMU_HFPERCLKEN0, 11, 0, NULL);
clk[clk_HFPERCLKI2C1] = clk_register_gate(NULL, "HFPERCLK.I2C1",
"HFXO", 0, base + CMU_HFPERCLKEN0, 12, 0, NULL);
clk[clk_HFPERCLKGPIO] = clk_register_gate(NULL, "HFPERCLK.GPIO",
"HFXO", 0, base + CMU_HFPERCLKEN0, 13, 0, NULL);
clk[clk_HFPERCLKVCMP] = clk_register_gate(NULL, "HFPERCLK.VCMP",
"HFXO", 0, base + CMU_HFPERCLKEN0, 14, 0, NULL);
clk[clk_HFPERCLKPRS] = clk_register_gate(NULL, "HFPERCLK.PRS",
"HFXO", 0, base + CMU_HFPERCLKEN0, 15, 0, NULL);
clk[clk_HFPERCLKADC0] = clk_register_gate(NULL, "HFPERCLK.ADC0",
"HFXO", 0, base + CMU_HFPERCLKEN0, 16, 0, NULL);
clk[clk_HFPERCLKDAC0] = clk_register_gate(NULL, "HFPERCLK.DAC0",
"HFXO", 0, base + CMU_HFPERCLKEN0, 17, 0, NULL);
return of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
}
CLK_OF_DECLARE(efm32ggcmu, "efm32gg,cmu", efm32gg_cmu_init);
...@@ -391,14 +391,8 @@ static int wm831x_clk_probe(struct platform_device *pdev) ...@@ -391,14 +391,8 @@ static int wm831x_clk_probe(struct platform_device *pdev)
return 0; return 0;
} }
static int wm831x_clk_remove(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver wm831x_clk_driver = { static struct platform_driver wm831x_clk_driver = {
.probe = wm831x_clk_probe, .probe = wm831x_clk_probe,
.remove = wm831x_clk_remove,
.driver = { .driver = {
.name = "wm831x-clk", .name = "wm831x-clk",
.owner = THIS_MODULE, .owner = THIS_MODULE,
......
This diff is collapsed.
...@@ -1080,13 +1080,16 @@ unsigned long clk_get_rate(struct clk *clk) ...@@ -1080,13 +1080,16 @@ unsigned long clk_get_rate(struct clk *clk)
} }
EXPORT_SYMBOL_GPL(clk_get_rate); EXPORT_SYMBOL_GPL(clk_get_rate);
static u8 clk_fetch_parent_index(struct clk *clk, struct clk *parent) static int clk_fetch_parent_index(struct clk *clk, struct clk *parent)
{ {
u8 i; int i;
if (!clk->parents) {
clk->parents = kcalloc(clk->num_parents,
sizeof(struct clk *), GFP_KERNEL);
if (!clk->parents) if (!clk->parents)
clk->parents = kzalloc((sizeof(struct clk*) * clk->num_parents), return -ENOMEM;
GFP_KERNEL); }
/* /*
* find index of new parent clock using cached parent ptrs, * find index of new parent clock using cached parent ptrs,
...@@ -1094,16 +1097,19 @@ static u8 clk_fetch_parent_index(struct clk *clk, struct clk *parent) ...@@ -1094,16 +1097,19 @@ static u8 clk_fetch_parent_index(struct clk *clk, struct clk *parent)
* them now to avoid future calls to __clk_lookup. * them now to avoid future calls to __clk_lookup.
*/ */
for (i = 0; i < clk->num_parents; i++) { for (i = 0; i < clk->num_parents; i++) {
if (clk->parents && clk->parents[i] == parent) if (clk->parents[i] == parent)
break; return i;
else if (!strcmp(clk->parent_names[i], parent->name)) {
if (clk->parents) if (clk->parents[i])
continue;
if (!strcmp(clk->parent_names[i], parent->name)) {
clk->parents[i] = __clk_lookup(parent->name); clk->parents[i] = __clk_lookup(parent->name);
break; return i;
} }
} }
return i; return -EINVAL;
} }
static void clk_reparent(struct clk *clk, struct clk *new_parent) static void clk_reparent(struct clk *clk, struct clk *new_parent)
...@@ -1265,7 +1271,7 @@ static struct clk *clk_calc_new_rates(struct clk *clk, unsigned long rate) ...@@ -1265,7 +1271,7 @@ static struct clk *clk_calc_new_rates(struct clk *clk, unsigned long rate)
struct clk *old_parent, *parent; struct clk *old_parent, *parent;
unsigned long best_parent_rate = 0; unsigned long best_parent_rate = 0;
unsigned long new_rate; unsigned long new_rate;
u8 p_index = 0; int p_index = 0;
/* sanity */ /* sanity */
if (IS_ERR_OR_NULL(clk)) if (IS_ERR_OR_NULL(clk))
...@@ -1306,7 +1312,7 @@ static struct clk *clk_calc_new_rates(struct clk *clk, unsigned long rate) ...@@ -1306,7 +1312,7 @@ static struct clk *clk_calc_new_rates(struct clk *clk, unsigned long rate)
/* try finding the new parent index */ /* try finding the new parent index */
if (parent) { if (parent) {
p_index = clk_fetch_parent_index(clk, parent); p_index = clk_fetch_parent_index(clk, parent);
if (p_index == clk->num_parents) { if (p_index < 0) {
pr_debug("%s: clk %s can not be parent of clk %s\n", pr_debug("%s: clk %s can not be parent of clk %s\n",
__func__, parent->name, clk->name); __func__, parent->name, clk->name);
return NULL; return NULL;
...@@ -1532,7 +1538,7 @@ static struct clk *__clk_init_parent(struct clk *clk) ...@@ -1532,7 +1538,7 @@ static struct clk *__clk_init_parent(struct clk *clk)
if (!clk->parents) if (!clk->parents)
clk->parents = clk->parents =
kzalloc((sizeof(struct clk*) * clk->num_parents), kcalloc(clk->num_parents, sizeof(struct clk *),
GFP_KERNEL); GFP_KERNEL);
ret = clk_get_parent_by_index(clk, index); ret = clk_get_parent_by_index(clk, index);
...@@ -1568,7 +1574,7 @@ void __clk_reparent(struct clk *clk, struct clk *new_parent) ...@@ -1568,7 +1574,7 @@ void __clk_reparent(struct clk *clk, struct clk *new_parent)
int clk_set_parent(struct clk *clk, struct clk *parent) int clk_set_parent(struct clk *clk, struct clk *parent)
{ {
int ret = 0; int ret = 0;
u8 p_index = 0; int p_index = 0;
unsigned long p_rate = 0; unsigned long p_rate = 0;
if (!clk) if (!clk)
...@@ -1597,10 +1603,10 @@ int clk_set_parent(struct clk *clk, struct clk *parent) ...@@ -1597,10 +1603,10 @@ int clk_set_parent(struct clk *clk, struct clk *parent)
if (parent) { if (parent) {
p_index = clk_fetch_parent_index(clk, parent); p_index = clk_fetch_parent_index(clk, parent);
p_rate = parent->rate; p_rate = parent->rate;
if (p_index == clk->num_parents) { if (p_index < 0) {
pr_debug("%s: clk %s can not be parent of clk %s\n", pr_debug("%s: clk %s can not be parent of clk %s\n",
__func__, parent->name, clk->name); __func__, parent->name, clk->name);
ret = -EINVAL; ret = p_index;
goto out; goto out;
} }
} }
...@@ -1689,7 +1695,7 @@ int __clk_init(struct device *dev, struct clk *clk) ...@@ -1689,7 +1695,7 @@ int __clk_init(struct device *dev, struct clk *clk)
* for clock drivers to statically initialize clk->parents. * for clock drivers to statically initialize clk->parents.
*/ */
if (clk->num_parents > 1 && !clk->parents) { if (clk->num_parents > 1 && !clk->parents) {
clk->parents = kzalloc((sizeof(struct clk*) * clk->num_parents), clk->parents = kcalloc(clk->num_parents, sizeof(struct clk *),
GFP_KERNEL); GFP_KERNEL);
/* /*
* __clk_lookup returns NULL for parents that have not been * __clk_lookup returns NULL for parents that have not been
...@@ -1830,7 +1836,7 @@ static int _clk_register(struct device *dev, struct clk_hw *hw, struct clk *clk) ...@@ -1830,7 +1836,7 @@ static int _clk_register(struct device *dev, struct clk_hw *hw, struct clk *clk)
hw->clk = clk; hw->clk = clk;
/* allocate local copy in case parent_names is __initdata */ /* allocate local copy in case parent_names is __initdata */
clk->parent_names = kzalloc((sizeof(char*) * clk->num_parents), clk->parent_names = kcalloc(clk->num_parents, sizeof(char *),
GFP_KERNEL); GFP_KERNEL);
if (!clk->parent_names) { if (!clk->parent_names) {
...@@ -2196,6 +2202,12 @@ struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec) ...@@ -2196,6 +2202,12 @@ struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec)
return clk; return clk;
} }
int of_clk_get_parent_count(struct device_node *np)
{
return of_count_phandle_with_args(np, "clocks", "#clock-cells");
}
EXPORT_SYMBOL_GPL(of_clk_get_parent_count);
const char *of_clk_get_parent_name(struct device_node *np, int index) const char *of_clk_get_parent_name(struct device_node *np, int index)
{ {
struct of_phandle_args clkspec; struct of_phandle_args clkspec;
......
obj-y += pll.o gate.o
/*
* Clock driver for Keystone 2 based devices
*
* Copyright (C) 2013 Texas Instruments.
* Murali Karicheri <m-karicheri2@ti.com>
* Santosh Shilimkar <santosh.shilimkar@ti.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of.h>
#include <linux/module.h>
/* PSC register offsets */
#define PTCMD 0x120
#define PTSTAT 0x128
#define PDSTAT 0x200
#define PDCTL 0x300
#define MDSTAT 0x800
#define MDCTL 0xa00
/* PSC module states */
#define PSC_STATE_SWRSTDISABLE 0
#define PSC_STATE_SYNCRST 1
#define PSC_STATE_DISABLE 2
#define PSC_STATE_ENABLE 3
#define MDSTAT_STATE_MASK 0x3f
#define MDSTAT_MCKOUT BIT(12)
#define PDSTAT_STATE_MASK 0x1f
#define MDCTL_FORCE BIT(31)
#define MDCTL_LRESET BIT(8)
#define PDCTL_NEXT BIT(0)
/* Maximum timeout to bail out state transition for module */
#define STATE_TRANS_MAX_COUNT 0xffff
static void __iomem *domain_transition_base;
/**
* struct clk_psc_data - PSC data
* @control_base: Base address for a PSC control
* @domain_base: Base address for a PSC domain
* @domain_id: PSC domain id number
*/
struct clk_psc_data {
void __iomem *control_base;
void __iomem *domain_base;
u32 domain_id;
};
/**
* struct clk_psc - PSC clock structure
* @hw: clk_hw for the psc
* @psc_data: PSC driver specific data
* @lock: Spinlock used by the driver
*/
struct clk_psc {
struct clk_hw hw;
struct clk_psc_data *psc_data;
spinlock_t *lock;
};
static DEFINE_SPINLOCK(psc_lock);
#define to_clk_psc(_hw) container_of(_hw, struct clk_psc, hw)
static void psc_config(void __iomem *control_base, void __iomem *domain_base,
u32 next_state, u32 domain_id)
{
u32 ptcmd, pdstat, pdctl, mdstat, mdctl, ptstat;
u32 count = STATE_TRANS_MAX_COUNT;
mdctl = readl(control_base + MDCTL);
mdctl &= ~MDSTAT_STATE_MASK;
mdctl |= next_state;
/* For disable, we always put the module in local reset */
if (next_state == PSC_STATE_DISABLE)
mdctl &= ~MDCTL_LRESET;
writel(mdctl, control_base + MDCTL);
pdstat = readl(domain_base + PDSTAT);
if (!(pdstat & PDSTAT_STATE_MASK)) {
pdctl = readl(domain_base + PDCTL);
pdctl |= PDCTL_NEXT;
writel(pdctl, domain_base + PDCTL);
}
ptcmd = 1 << domain_id;
writel(ptcmd, domain_transition_base + PTCMD);
do {
ptstat = readl(domain_transition_base + PTSTAT);
} while (((ptstat >> domain_id) & 1) && count--);
count = STATE_TRANS_MAX_COUNT;
do {
mdstat = readl(control_base + MDSTAT);
} while (!((mdstat & MDSTAT_STATE_MASK) == next_state) && count--);
}
static int keystone_clk_is_enabled(struct clk_hw *hw)
{
struct clk_psc *psc = to_clk_psc(hw);
struct clk_psc_data *data = psc->psc_data;
u32 mdstat = readl(data->control_base + MDSTAT);
return (mdstat & MDSTAT_MCKOUT) ? 1 : 0;
}
static int keystone_clk_enable(struct clk_hw *hw)
{
struct clk_psc *psc = to_clk_psc(hw);
struct clk_psc_data *data = psc->psc_data;
unsigned long flags = 0;
if (psc->lock)
spin_lock_irqsave(psc->lock, flags);
psc_config(data->control_base, data->domain_base,
PSC_STATE_ENABLE, data->domain_id);
if (psc->lock)
spin_unlock_irqrestore(psc->lock, flags);
return 0;
}
static void keystone_clk_disable(struct clk_hw *hw)
{
struct clk_psc *psc = to_clk_psc(hw);
struct clk_psc_data *data = psc->psc_data;
unsigned long flags = 0;
if (psc->lock)
spin_lock_irqsave(psc->lock, flags);
psc_config(data->control_base, data->domain_base,
PSC_STATE_DISABLE, data->domain_id);
if (psc->lock)
spin_unlock_irqrestore(psc->lock, flags);
}
static const struct clk_ops clk_psc_ops = {
.enable = keystone_clk_enable,
.disable = keystone_clk_disable,
.is_enabled = keystone_clk_is_enabled,
};
/**
* clk_register_psc - register psc clock
* @dev: device that is registering this clock
* @name: name of this clock
* @parent_name: name of clock's parent
* @psc_data: platform data to configure this clock
* @lock: spinlock used by this clock
*/
static struct clk *clk_register_psc(struct device *dev,
const char *name,
const char *parent_name,
struct clk_psc_data *psc_data,
spinlock_t *lock)
{
struct clk_init_data init;
struct clk_psc *psc;
struct clk *clk;
psc = kzalloc(sizeof(*psc), GFP_KERNEL);
if (!psc)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_psc_ops;
init.parent_names = (parent_name ? &parent_name : NULL);
init.num_parents = (parent_name ? 1 : 0);
psc->psc_data = psc_data;
psc->lock = lock;
psc->hw.init = &init;
clk = clk_register(NULL, &psc->hw);
if (IS_ERR(clk))
kfree(psc);
return clk;
}
/**
* of_psc_clk_init - initialize psc clock through DT
* @node: device tree node for this clock
* @lock: spinlock used by this clock
*/
static void __init of_psc_clk_init(struct device_node *node, spinlock_t *lock)
{
const char *clk_name = node->name;
const char *parent_name;
struct clk_psc_data *data;
struct clk *clk;
int i;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data) {
pr_err("%s: Out of memory\n", __func__);
return;
}
i = of_property_match_string(node, "reg-names", "control");
data->control_base = of_iomap(node, i);
if (!data->control_base) {
pr_err("%s: control ioremap failed\n", __func__);
goto out;
}
i = of_property_match_string(node, "reg-names", "domain");
data->domain_base = of_iomap(node, i);
if (!data->domain_base) {
pr_err("%s: domain ioremap failed\n", __func__);
iounmap(data->control_base);
goto out;
}
of_property_read_u32(node, "domain-id", &data->domain_id);
/* Domain transition registers at fixed address space of domain_id 0 */
if (!domain_transition_base && !data->domain_id)
domain_transition_base = data->domain_base;
of_property_read_string(node, "clock-output-names", &clk_name);
parent_name = of_clk_get_parent_name(node, 0);
if (!parent_name) {
pr_err("%s: Parent clock not found\n", __func__);
goto out;
}
clk = clk_register_psc(NULL, clk_name, parent_name, data, lock);
if (clk) {
of_clk_add_provider(node, of_clk_src_simple_get, clk);
return;
}
pr_err("%s: error registering clk %s\n", __func__, node->name);
out:
kfree(data);
return;
}
/**
* of_keystone_psc_clk_init - initialize psc clock through DT
* @node: device tree node for this clock
*/
static void __init of_keystone_psc_clk_init(struct device_node *node)
{
of_psc_clk_init(node, &psc_lock);
}
CLK_OF_DECLARE(keystone_gate_clk, "ti,keystone,psc-clock",
of_keystone_psc_clk_init);
/*
* PLL clock driver for Keystone devices
*
* Copyright (C) 2013 Texas Instruments Inc.
* Murali Karicheri <m-karicheri2@ti.com>
* Santosh Shilimkar <santosh.shilimkar@ti.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of.h>
#include <linux/module.h>
#define PLLM_LOW_MASK 0x3f
#define PLLM_HIGH_MASK 0x7ffc0
#define MAIN_PLLM_HIGH_MASK 0x7f000
#define PLLM_HIGH_SHIFT 6
#define PLLD_MASK 0x3f
/**
* struct clk_pll_data - pll data structure
* @has_pllctrl: If set to non zero, lower 6 bits of multiplier is in pllm
* register of pll controller, else it is in the pll_ctrl0((bit 11-6)
* @phy_pllm: Physical address of PLLM in pll controller. Used when
* has_pllctrl is non zero.
* @phy_pll_ctl0: Physical address of PLL ctrl0. This could be that of
* Main PLL or any other PLLs in the device such as ARM PLL, DDR PLL
* or PA PLL available on keystone2. These PLLs are controlled by
* this register. Main PLL is controlled by a PLL controller.
* @pllm: PLL register map address
* @pll_ctl0: PLL controller map address
* @pllm_lower_mask: multiplier lower mask
* @pllm_upper_mask: multiplier upper mask
* @pllm_upper_shift: multiplier upper shift
* @plld_mask: divider mask
* @postdiv: Post divider
*/
struct clk_pll_data {
bool has_pllctrl;
u32 phy_pllm;
u32 phy_pll_ctl0;
void __iomem *pllm;
void __iomem *pll_ctl0;
u32 pllm_lower_mask;
u32 pllm_upper_mask;
u32 pllm_upper_shift;
u32 plld_mask;
u32 postdiv;
};
/**
* struct clk_pll - Main pll clock
* @hw: clk_hw for the pll
* @pll_data: PLL driver specific data
*/
struct clk_pll {
struct clk_hw hw;
struct clk_pll_data *pll_data;
};
#define to_clk_pll(_hw) container_of(_hw, struct clk_pll, hw)
static unsigned long clk_pllclk_recalc(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_pll *pll = to_clk_pll(hw);
struct clk_pll_data *pll_data = pll->pll_data;
unsigned long rate = parent_rate;
u32 mult = 0, prediv, postdiv, val;
/*
* get bits 0-5 of multiplier from pllctrl PLLM register
* if has_pllctrl is non zero
*/
if (pll_data->has_pllctrl) {
val = readl(pll_data->pllm);
mult = (val & pll_data->pllm_lower_mask);
}
/* bit6-12 of PLLM is in Main PLL control register */
val = readl(pll_data->pll_ctl0);
mult |= ((val & pll_data->pllm_upper_mask)
>> pll_data->pllm_upper_shift);
prediv = (val & pll_data->plld_mask);
postdiv = pll_data->postdiv;
rate /= (prediv + 1);
rate = (rate * (mult + 1));
rate /= postdiv;
return rate;
}
static const struct clk_ops clk_pll_ops = {
.recalc_rate = clk_pllclk_recalc,
};
static struct clk *clk_register_pll(struct device *dev,
const char *name,
const char *parent_name,
struct clk_pll_data *pll_data)
{
struct clk_init_data init;
struct clk_pll *pll;
struct clk *clk;
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
if (!pll)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_pll_ops;
init.flags = 0;
init.parent_names = (parent_name ? &parent_name : NULL);
init.num_parents = (parent_name ? 1 : 0);
pll->pll_data = pll_data;
pll->hw.init = &init;
clk = clk_register(NULL, &pll->hw);
if (IS_ERR(clk))
goto out;
return clk;
out:
kfree(pll);
return NULL;
}
/**
* _of_clk_init - PLL initialisation via DT
* @node: device tree node for this clock
* @pllctrl: If true, lower 6 bits of multiplier is in pllm register of
* pll controller, else it is in the control regsiter0(bit 11-6)
*/
static void __init _of_pll_clk_init(struct device_node *node, bool pllctrl)
{
struct clk_pll_data *pll_data;
const char *parent_name;
struct clk *clk;
int i;
pll_data = kzalloc(sizeof(*pll_data), GFP_KERNEL);
if (!pll_data) {
pr_err("%s: Out of memory\n", __func__);
return;
}
parent_name = of_clk_get_parent_name(node, 0);
if (of_property_read_u32(node, "fixed-postdiv", &pll_data->postdiv))
goto out;
i = of_property_match_string(node, "reg-names", "control");
pll_data->pll_ctl0 = of_iomap(node, i);
if (!pll_data->pll_ctl0) {
pr_err("%s: ioremap failed\n", __func__);
goto out;
}
pll_data->pllm_lower_mask = PLLM_LOW_MASK;
pll_data->pllm_upper_shift = PLLM_HIGH_SHIFT;
pll_data->plld_mask = PLLD_MASK;
pll_data->has_pllctrl = pllctrl;
if (!pll_data->has_pllctrl) {
pll_data->pllm_upper_mask = PLLM_HIGH_MASK;
} else {
pll_data->pllm_upper_mask = MAIN_PLLM_HIGH_MASK;
i = of_property_match_string(node, "reg-names", "multiplier");
pll_data->pllm = of_iomap(node, i);
if (!pll_data->pllm) {
iounmap(pll_data->pll_ctl0);
goto out;
}
}
clk = clk_register_pll(NULL, node->name, parent_name, pll_data);
if (clk) {
of_clk_add_provider(node, of_clk_src_simple_get, clk);
return;
}
out:
pr_err("%s: error initializing pll %s\n", __func__, node->name);
kfree(pll_data);
}
/**
* of_keystone_pll_clk_init - PLL initialisation DT wrapper
* @node: device tree node for this clock
*/
static void __init of_keystone_pll_clk_init(struct device_node *node)
{
_of_pll_clk_init(node, false);
}
CLK_OF_DECLARE(keystone_pll_clock, "ti,keystone,pll-clock",
of_keystone_pll_clk_init);
/**
* of_keystone_pll_main_clk_init - Main PLL initialisation DT wrapper
* @node: device tree node for this clock
*/
static void __init of_keystone_main_pll_clk_init(struct device_node *node)
{
_of_pll_clk_init(node, true);
}
CLK_OF_DECLARE(keystone_main_pll_clock, "ti,keystone,main-pll-clock",
of_keystone_main_pll_clk_init);
/**
* of_pll_div_clk_init - PLL divider setup function
* @node: device tree node for this clock
*/
static void __init of_pll_div_clk_init(struct device_node *node)
{
const char *parent_name;
void __iomem *reg;
u32 shift, mask;
struct clk *clk;
const char *clk_name = node->name;
of_property_read_string(node, "clock-output-names", &clk_name);
reg = of_iomap(node, 0);
if (!reg) {
pr_err("%s: ioremap failed\n", __func__);
return;
}
parent_name = of_clk_get_parent_name(node, 0);
if (!parent_name) {
pr_err("%s: missing parent clock\n", __func__);
return;
}
if (of_property_read_u32(node, "bit-shift", &shift)) {
pr_err("%s: missing 'shift' property\n", __func__);
return;
}
if (of_property_read_u32(node, "bit-mask", &mask)) {
pr_err("%s: missing 'bit-mask' property\n", __func__);
return;
}
clk = clk_register_divider(NULL, clk_name, parent_name, 0, reg, shift,
mask, 0, NULL);
if (clk)
of_clk_add_provider(node, of_clk_src_simple_get, clk);
else
pr_err("%s: error registering divider %s\n", __func__, clk_name);
}
CLK_OF_DECLARE(pll_divider_clock, "ti,keystone,pll-divider-clock", of_pll_div_clk_init);
/**
* of_pll_mux_clk_init - PLL mux setup function
* @node: device tree node for this clock
*/
static void __init of_pll_mux_clk_init(struct device_node *node)
{
void __iomem *reg;
u32 shift, mask;
struct clk *clk;
const char *parents[2];
const char *clk_name = node->name;
of_property_read_string(node, "clock-output-names", &clk_name);
reg = of_iomap(node, 0);
if (!reg) {
pr_err("%s: ioremap failed\n", __func__);
return;
}
parents[0] = of_clk_get_parent_name(node, 0);
parents[1] = of_clk_get_parent_name(node, 1);
if (!parents[0] || !parents[1]) {
pr_err("%s: missing parent clocks\n", __func__);
return;
}
if (of_property_read_u32(node, "bit-shift", &shift)) {
pr_err("%s: missing 'shift' property\n", __func__);
return;
}
if (of_property_read_u32(node, "bit-mask", &mask)) {
pr_err("%s: missing 'bit-mask' property\n", __func__);
return;
}
clk = clk_register_mux(NULL, clk_name, (const char **)&parents,
ARRAY_SIZE(parents) , 0, reg, shift, mask,
0, NULL);
if (clk)
of_clk_add_provider(node, of_clk_src_simple_get, clk);
else
pr_err("%s: error registering mux %s\n", __func__, clk_name);
}
CLK_OF_DECLARE(pll_mux_clock, "ti,keystone,pll-mux-clock", of_pll_mux_clk_init);
...@@ -117,13 +117,19 @@ static void __init zynq_clk_register_fclk(enum zynq_clk fclk, ...@@ -117,13 +117,19 @@ static void __init zynq_clk_register_fclk(enum zynq_clk fclk,
goto err; goto err;
fclk_gate_lock = kmalloc(sizeof(*fclk_gate_lock), GFP_KERNEL); fclk_gate_lock = kmalloc(sizeof(*fclk_gate_lock), GFP_KERNEL);
if (!fclk_gate_lock) if (!fclk_gate_lock)
goto err; goto err_fclk_gate_lock;
spin_lock_init(fclk_lock); spin_lock_init(fclk_lock);
spin_lock_init(fclk_gate_lock); spin_lock_init(fclk_gate_lock);
mux_name = kasprintf(GFP_KERNEL, "%s_mux", clk_name); mux_name = kasprintf(GFP_KERNEL, "%s_mux", clk_name);
if (!mux_name)
goto err_mux_name;
div0_name = kasprintf(GFP_KERNEL, "%s_div0", clk_name); div0_name = kasprintf(GFP_KERNEL, "%s_div0", clk_name);
if (!div0_name)
goto err_div0_name;
div1_name = kasprintf(GFP_KERNEL, "%s_div1", clk_name); div1_name = kasprintf(GFP_KERNEL, "%s_div1", clk_name);
if (!div1_name)
goto err_div1_name;
clk = clk_register_mux(NULL, mux_name, parents, 4, clk = clk_register_mux(NULL, mux_name, parents, 4,
CLK_SET_RATE_NO_REPARENT, fclk_ctrl_reg, 4, 2, 0, CLK_SET_RATE_NO_REPARENT, fclk_ctrl_reg, 4, 2, 0,
...@@ -147,6 +153,14 @@ static void __init zynq_clk_register_fclk(enum zynq_clk fclk, ...@@ -147,6 +153,14 @@ static void __init zynq_clk_register_fclk(enum zynq_clk fclk,
return; return;
err_div1_name:
kfree(div0_name);
err_div0_name:
kfree(mux_name);
err_mux_name:
kfree(fclk_gate_lock);
err_fclk_gate_lock:
kfree(fclk_lock);
err: err:
clks[fclk] = ERR_PTR(-ENOMEM); clks[fclk] = ERR_PTR(-ENOMEM);
} }
......
#ifndef __DT_BINDINGS_CLOCK_EFM32_CMU_H
#define __DT_BINDINGS_CLOCK_EFM32_CMU_H
#define clk_HFXO 0
#define clk_HFRCO 1
#define clk_LFXO 2
#define clk_LFRCO 3
#define clk_ULFRCO 4
#define clk_AUXHFRCO 5
#define clk_HFCLKNODIV 6
#define clk_HFCLK 7
#define clk_HFPERCLK 8
#define clk_HFCORECLK 9
#define clk_LFACLK 10
#define clk_LFBCLK 11
#define clk_WDOGCLK 12
#define clk_HFCORECLKDMA 13
#define clk_HFCORECLKAES 14
#define clk_HFCORECLKUSBC 15
#define clk_HFCORECLKUSB 16
#define clk_HFCORECLKLE 17
#define clk_HFCORECLKEBI 18
#define clk_HFPERCLKUSART0 19
#define clk_HFPERCLKUSART1 20
#define clk_HFPERCLKUSART2 21
#define clk_HFPERCLKUART0 22
#define clk_HFPERCLKUART1 23
#define clk_HFPERCLKTIMER0 24
#define clk_HFPERCLKTIMER1 25
#define clk_HFPERCLKTIMER2 26
#define clk_HFPERCLKTIMER3 27
#define clk_HFPERCLKACMP0 28
#define clk_HFPERCLKACMP1 29
#define clk_HFPERCLKI2C0 30
#define clk_HFPERCLKI2C1 31
#define clk_HFPERCLKGPIO 32
#define clk_HFPERCLKVCMP 33
#define clk_HFPERCLKPRS 34
#define clk_HFPERCLKADC0 35
#define clk_HFPERCLKDAC0 36
#endif /* __DT_BINDINGS_CLOCK_EFM32_CMU_H */
...@@ -472,6 +472,7 @@ void of_clk_del_provider(struct device_node *np); ...@@ -472,6 +472,7 @@ void of_clk_del_provider(struct device_node *np);
struct clk *of_clk_src_simple_get(struct of_phandle_args *clkspec, struct clk *of_clk_src_simple_get(struct of_phandle_args *clkspec,
void *data); void *data);
struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data); struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data);
int of_clk_get_parent_count(struct device_node *np);
const char *of_clk_get_parent_name(struct device_node *np, int index); const char *of_clk_get_parent_name(struct device_node *np, int index);
void of_clk_init(const struct of_device_id *matches); void of_clk_init(const struct of_device_id *matches);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment