[PATCH v5 02/19] clk: at91: Add support for sam9x60 USB clock

Sean Anderson seanga2 at gmail.com
Wed Jan 4 18:46:47 CET 2023


On 12/22/22 05:53, Sergiu Moga wrote:
> Implement sam9x60 USB clock driver. This clock has
> three parents: PLLA, UPLL and MAINXTAL. The driver is
> aware of the three possible parents with the help of the
> two mux tables provied to the driver during the registration
> of the clock.
> 
> Signed-off-by: Sergiu Moga <sergiu.moga at microchip.com>
> ---
> 
> 
> v1 -> v4:
> - No change
> 
> 
> v4 -> v5:
> - fix warning regarding below 0 check on unsigned variable
> 
> 
>   drivers/clk/at91/Kconfig           |   7 ++
>   drivers/clk/at91/Makefile          |   1 +
>   drivers/clk/at91/clk-sam9x60-usb.c | 157 +++++++++++++++++++++++++++++
>   drivers/clk/at91/pmc.h             |  11 ++
>   4 files changed, 176 insertions(+)
>   create mode 100644 drivers/clk/at91/clk-sam9x60-usb.c
> 
> diff --git a/drivers/clk/at91/Kconfig b/drivers/clk/at91/Kconfig
> index 4abc8026b4..4563892647 100644
> --- a/drivers/clk/at91/Kconfig
> +++ b/drivers/clk/at91/Kconfig
> @@ -61,3 +61,10 @@ config AT91_SAM9X60_PLL
>   	help
>   	  This option is used to enable the AT91 SAM9X60's PLL clock
>   	  driver.
> +
> +config AT91_SAM9X60_USB
> +	bool "USB Clock support for SAM9X60 SoCs"
> +	depends on CLK_AT91
> +	help
> +	  This option is used to enable the AT91 SAM9X60's USB clock
> +	  driver.
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> index 580b406d7b..e53dcb4ca7 100644
> --- a/drivers/clk/at91/Makefile
> +++ b/drivers/clk/at91/Makefile
> @@ -9,6 +9,7 @@ obj-y += clk-peripheral.o
>   obj-$(CONFIG_AT91_GENERIC_CLK)	+= clk-generic.o
>   obj-$(CONFIG_AT91_UTMI)		+= clk-utmi.o
>   obj-$(CONFIG_AT91_SAM9X60_PLL)	+= clk-sam9x60-pll.o
> +obj-$(CONFIG_AT91_SAM9X60_USB)	+= clk-sam9x60-usb.o
>   obj-$(CONFIG_SAMA7G5)		+= sama7g5.o
>   obj-$(CONFIG_SAM9X60)		+= sam9x60.o
>   else
> diff --git a/drivers/clk/at91/clk-sam9x60-usb.c b/drivers/clk/at91/clk-sam9x60-usb.c
> new file mode 100644
> index 0000000000..798fa9eb3c
> --- /dev/null
> +++ b/drivers/clk/at91/clk-sam9x60-usb.c
> @@ -0,0 +1,157 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * SAM9X60's USB Clock support.
> + *
> + * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
> + *
> + * Author: Sergiu Moga <sergiu.moga at microchip.com>
> + */
> +
> +#include <clk-uclass.h>
> +#include <dm.h>
> +#include <linux/clk-provider.h>
> +
> +#include "pmc.h"
> +
> +#define UBOOT_DM_CLK_AT91_SAM9X60_USB		"at91-sam9x60-usb-clk"
> +
> +struct sam9x60_usb {
> +	const struct clk_usbck_layout		*layout;
> +	void					__iomem *base;
> +	struct clk				clk;
> +	const u32				*clk_mux_table;
> +	const u32				*mux_table;
> +	const char * const			*parent_names;
> +	u32					num_parents;
> +	u8					id;
> +};
> +
> +#define to_sam9x60_usb(_clk)	container_of(_clk, struct sam9x60_usb, clk)
> +#define USB_MAX_DIV		15
> +
> +static int sam9x60_usb_clk_set_parent(struct clk *clk, struct clk *parent)
> +{
> +	struct sam9x60_usb *usb = to_sam9x60_usb(clk);
> +	int index;
> +	u32 val;
> +
> +	index = at91_clk_mux_val_to_index(usb->clk_mux_table, usb->num_parents,
> +					  parent->id);
> +	if (index < 0)
> +		return index;
> +
> +	index = at91_clk_mux_index_to_val(usb->mux_table, usb->num_parents,
> +					  index);
> +	if (index < 0)
> +		return index;
> +
> +	pmc_read(usb->base, usb->layout->offset, &val);
> +	val &= ~usb->layout->usbs_mask;
> +	val |= index << (ffs(usb->layout->usbs_mask - 1));

If you can't use FIELD_GET/PREP because the layout is dynamic, is
may be better to just store the shift/mask separately. Depends on
whether ffs is expensive on your arch, or whether it has its own
instruction.

> +	pmc_write(usb->base, usb->layout->offset, val);
> +
> +	return 0;
> +}
> +
> +static ulong sam9x60_usb_clk_get_rate(struct clk *clk)
> +{
> +	struct sam9x60_usb *usb = to_sam9x60_usb(clk);
> +	ulong parent_rate = clk_get_parent_rate(clk);
> +	u32 val, usbdiv;
> +
> +	if (!parent_rate)
> +		return 0;
> +
> +	pmc_read(usb->base, usb->layout->offset, &val);
> +	usbdiv = (val & usb->layout->usbdiv_mask) >>
> +		(ffs(usb->layout->usbdiv_mask) - 1);
> +	return parent_rate / (usbdiv + 1);
> +}
> +
> +static ulong sam9x60_usb_clk_set_rate(struct clk *clk, ulong rate)
> +{
> +	struct sam9x60_usb *usb = to_sam9x60_usb(clk);
> +	ulong parent_rate = clk_get_parent_rate(clk);
> +	u32 usbdiv, val;
> +
> +	if (!parent_rate)
> +		return 0;
> +
> +	usbdiv = DIV_ROUND_CLOSEST(parent_rate, rate);
> +	if (usbdiv > USB_MAX_DIV + 1 || !usbdiv)
> +		return 0;
> +
> +	pmc_read(usb->base, usb->layout->offset, &val);
> +	val &= usb->layout->usbdiv_mask;
> +	val |= (usbdiv - 1) << (ffs(usb->layout->usbdiv_mask) - 1);
> +	pmc_write(usb->base, usb->layout->offset, val);
> +
> +	return parent_rate / usbdiv;
> +}
> +
> +static const struct clk_ops sam9x60_usb_ops = {
> +	.set_parent = sam9x60_usb_clk_set_parent,
> +	.set_rate = sam9x60_usb_clk_set_rate,
> +	.get_rate = sam9x60_usb_clk_get_rate,
> +};
> +
> +struct clk *
> +sam9x60_clk_register_usb(void __iomem *base,  const char *name,
> +			 const char * const *parent_names, u8 num_parents,
> +			 const struct clk_usbck_layout *usbck_layout,
> +			 const u32 *clk_mux_table, const u32 *mux_table, u8 id)
> +{
> +	struct sam9x60_usb *usb;
> +	struct clk *clk;
> +	int ret, index;
> +	u32 val;
> +
> +	if (!base || !name || !parent_names || !num_parents ||
> +	    !clk_mux_table || !mux_table)
> +		return ERR_PTR(-EINVAL);
> +
> +	usb = kzalloc(sizeof(*usb), GFP_KERNEL);
> +	if (!usb)
> +		return ERR_PTR(-ENOMEM);
> +
> +	usb->id = id;
> +	usb->base = base;
> +	usb->layout = usbck_layout;
> +	usb->parent_names = parent_names;
> +	usb->num_parents = num_parents;
> +	usb->clk_mux_table = clk_mux_table;
> +	usb->mux_table = mux_table;
> +
> +	clk = &usb->clk;
> +	clk->flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
> +		     CLK_SET_RATE_PARENT;
> +
> +	pmc_read(usb->base, usb->layout->offset, &val);
> +
> +	val = (val & usb->layout->usbs_mask) >>
> +		(ffs(usb->layout->usbs_mask) - 1);
> +
> +	index = at91_clk_mux_val_to_index(usb->mux_table, usb->num_parents,
> +					  val);
> +
> +	if (index < 0) {
> +		kfree(usb);
> +		return ERR_PTR(index);
> +	}
> +
> +	ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAM9X60_USB, name,
> +			   parent_names[index]);
> +	if (ret) {
> +		kfree(usb);
> +		clk = ERR_PTR(ret);
> +	}
> +
> +	return clk;
> +}
> +
> +U_BOOT_DRIVER(at91_sam9x60_usb_clk) = {
> +	.name = UBOOT_DM_CLK_AT91_SAM9X60_USB,
> +	.id = UCLASS_CLK,
> +	.ops = &sam9x60_usb_ops,
> +	.flags = DM_FLAG_PRE_RELOC,
> +};
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index 2b4dd9a3d9..17793b8802 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -71,6 +71,12 @@ struct clk_pcr_layout {
>   	u32 pid_mask;
>   };
>   
> +struct clk_usbck_layout {
> +	u32 offset;
> +	u32 usbs_mask;
> +	u32 usbdiv_mask;
> +};
> +
>   extern const struct clk_programmable_layout at91rm9200_programmable_layout;
>   extern const struct clk_programmable_layout at91sam9g45_programmable_layout;
>   extern const struct clk_programmable_layout at91sam9x5_programmable_layout;
> @@ -87,6 +93,11 @@ struct clk *at91_clk_sam9x5_main(void __iomem *reg, const char *name,
>   			const char * const *parent_names, int num_parents,
>   			const u32 *mux_table, int type);
>   struct clk *
> +sam9x60_clk_register_usb(void __iomem *base,  const char *name,
> +			 const char * const *parent_names, u8 num_parents,
> +			 const struct clk_usbck_layout *usbck_layout,
> +			 const u32 *clk_mux_table, const u32 *mux_table, u8 id);
> +struct clk *
>   sam9x60_clk_register_div_pll(void __iomem *base, const char *name,
>   			const char *parent_name, u8 id,
>   			const struct clk_pll_characteristics *characteristics,

Reviewed-by: Sean Anderson <seanga2 at gmail.com>


More information about the U-Boot mailing list