1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Peripheral clock support for AT91 architectures. 4 * 5 * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries 6 * 7 * Author: Claudiu Beznea <claudiu.beznea@microchip.com> 8 * 9 * Based on drivers/clk/at91/clk-peripheral.c from Linux. 10 */ 11#include <common.h> 12#include <clk-uclass.h> 13#include <dm.h> 14#include <linux/io.h> 15#include <linux/clk-provider.h> 16#include <linux/clk/at91_pmc.h> 17 18#include "pmc.h" 19 20#define UBOOT_DM_CLK_AT91_PERIPH "at91-periph-clk" 21#define UBOOT_DM_CLK_AT91_SAM9X5_PERIPH "at91-sam9x5-periph-clk" 22 23#define PERIPHERAL_ID_MIN 2 24#define PERIPHERAL_ID_MAX 31 25#define PERIPHERAL_MASK(id) (1 << ((id) & PERIPHERAL_ID_MAX)) 26 27#define PERIPHERAL_MAX_SHIFT 3 28 29struct clk_peripheral { 30 void __iomem *base; 31 struct clk clk; 32 u32 id; 33}; 34 35#define to_clk_peripheral(_c) container_of(_c, struct clk_peripheral, clk) 36 37struct clk_sam9x5_peripheral { 38 const struct clk_pcr_layout *layout; 39 void __iomem *base; 40 struct clk clk; 41 struct clk_range range; 42 u32 id; 43 u32 div; 44 bool auto_div; 45}; 46 47#define to_clk_sam9x5_peripheral(_c) \ 48 container_of(_c, struct clk_sam9x5_peripheral, clk) 49 50static int clk_peripheral_enable(struct clk *clk) 51{ 52 struct clk_peripheral *periph = to_clk_peripheral(clk); 53 int offset = AT91_PMC_PCER; 54 u32 id = periph->id; 55 56 if (id < PERIPHERAL_ID_MIN) 57 return 0; 58 if (id > PERIPHERAL_ID_MAX) 59 offset = AT91_PMC_PCER1; 60 pmc_write(periph->base, offset, PERIPHERAL_MASK(id)); 61 62 return 0; 63} 64 65static int clk_peripheral_disable(struct clk *clk) 66{ 67 struct clk_peripheral *periph = to_clk_peripheral(clk); 68 int offset = AT91_PMC_PCDR; 69 u32 id = periph->id; 70 71 if (id < PERIPHERAL_ID_MIN) 72 return -EINVAL; 73 74 if (id > PERIPHERAL_ID_MAX) 75 offset = AT91_PMC_PCDR1; 76 pmc_write(periph->base, offset, PERIPHERAL_MASK(id)); 77 78 return 0; 79} 80 81static const struct clk_ops peripheral_ops = { 82 .enable = clk_peripheral_enable, 83 .disable = clk_peripheral_disable, 84 .get_rate = clk_generic_get_rate, 85}; 86 87struct clk * 88at91_clk_register_peripheral(void __iomem *base, const char *name, 89 const char *parent_name, u32 id) 90{ 91 struct clk_peripheral *periph; 92 struct clk *clk; 93 int ret; 94 95 if (!base || !name || !parent_name || id > PERIPHERAL_ID_MAX) 96 return ERR_PTR(-EINVAL); 97 98 periph = kzalloc(sizeof(*periph), GFP_KERNEL); 99 if (!periph) 100 return ERR_PTR(-ENOMEM); 101 102 periph->id = id; 103 periph->base = base; 104 105 clk = &periph->clk; 106 clk->flags = CLK_GET_RATE_NOCACHE; 107 ret = clk_register(clk, UBOOT_DM_CLK_AT91_PERIPH, name, parent_name); 108 if (ret) { 109 kfree(periph); 110 clk = ERR_PTR(ret); 111 } 112 113 return clk; 114} 115 116U_BOOT_DRIVER(at91_periph_clk) = { 117 .name = UBOOT_DM_CLK_AT91_PERIPH, 118 .id = UCLASS_CLK, 119 .ops = &peripheral_ops, 120 .flags = DM_FLAG_PRE_RELOC, 121}; 122 123static int clk_sam9x5_peripheral_enable(struct clk *clk) 124{ 125 struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk); 126 127 if (periph->id < PERIPHERAL_ID_MIN) 128 return 0; 129 130 pmc_write(periph->base, periph->layout->offset, 131 (periph->id & periph->layout->pid_mask)); 132 pmc_update_bits(periph->base, periph->layout->offset, 133 periph->layout->cmd | AT91_PMC_PCR_EN, 134 periph->layout->cmd | AT91_PMC_PCR_EN); 135 136 return 0; 137} 138 139static int clk_sam9x5_peripheral_disable(struct clk *clk) 140{ 141 struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk); 142 143 if (periph->id < PERIPHERAL_ID_MIN) 144 return -EINVAL; 145 146 pmc_write(periph->base, periph->layout->offset, 147 (periph->id & periph->layout->pid_mask)); 148 pmc_update_bits(periph->base, periph->layout->offset, 149 AT91_PMC_PCR_EN | periph->layout->cmd, 150 periph->layout->cmd); 151 152 return 0; 153} 154 155static ulong clk_sam9x5_peripheral_get_rate(struct clk *clk) 156{ 157 struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk); 158 ulong parent_rate = clk_get_parent_rate(clk); 159 u32 val, shift = ffs(periph->layout->div_mask) - 1; 160 161 if (!parent_rate) 162 return 0; 163 164 pmc_write(periph->base, periph->layout->offset, 165 (periph->id & periph->layout->pid_mask)); 166 pmc_read(periph->base, periph->layout->offset, &val); 167 shift = (val & periph->layout->div_mask) >> shift; 168 169 return parent_rate >> shift; 170} 171 172static ulong clk_sam9x5_peripheral_set_rate(struct clk *clk, ulong rate) 173{ 174 struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk); 175 ulong parent_rate = clk_get_parent_rate(clk); 176 int shift; 177 178 if (!parent_rate) 179 return 0; 180 181 if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) { 182 if (parent_rate == rate) 183 return rate; 184 else 185 return 0; 186 } 187 188 if (periph->range.max && rate > periph->range.max) 189 return 0; 190 191 for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) { 192 if (parent_rate >> shift <= rate) 193 break; 194 } 195 if (shift == PERIPHERAL_MAX_SHIFT + 1) 196 return 0; 197 198 pmc_write(periph->base, periph->layout->offset, 199 (periph->id & periph->layout->pid_mask)); 200 pmc_update_bits(periph->base, periph->layout->offset, 201 periph->layout->div_mask | periph->layout->cmd, 202 (shift << (ffs(periph->layout->div_mask) - 1)) | 203 periph->layout->cmd); 204 205 return parent_rate >> shift; 206} 207 208static const struct clk_ops sam9x5_peripheral_ops = { 209 .enable = clk_sam9x5_peripheral_enable, 210 .disable = clk_sam9x5_peripheral_disable, 211 .get_rate = clk_sam9x5_peripheral_get_rate, 212 .set_rate = clk_sam9x5_peripheral_set_rate, 213}; 214 215struct clk * 216at91_clk_register_sam9x5_peripheral(void __iomem *base, 217 const struct clk_pcr_layout *layout, 218 const char *name, const char *parent_name, 219 u32 id, const struct clk_range *range) 220{ 221 struct clk_sam9x5_peripheral *periph; 222 struct clk *clk; 223 int ret; 224 225 if (!base || !layout || !name || !parent_name || !range) 226 return ERR_PTR(-EINVAL); 227 228 periph = kzalloc(sizeof(*periph), GFP_KERNEL); 229 if (!periph) 230 return ERR_PTR(-ENOMEM); 231 232 periph->id = id; 233 periph->base = base; 234 periph->layout = layout; 235 periph->range = *range; 236 237 clk = &periph->clk; 238 clk->flags = CLK_GET_RATE_NOCACHE; 239 ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAM9X5_PERIPH, name, 240 parent_name); 241 if (ret) { 242 kfree(periph); 243 clk = ERR_PTR(ret); 244 } 245 246 return clk; 247} 248 249U_BOOT_DRIVER(at91_sam9x5_periph_clk) = { 250 .name = UBOOT_DM_CLK_AT91_SAM9X5_PERIPH, 251 .id = UCLASS_CLK, 252 .ops = &sam9x5_peripheral_ops, 253 .flags = DM_FLAG_PRE_RELOC, 254}; 255