Path: blob/main/sys/dev/clk/xilinx/zynqmp_clock.c
105416 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2023 Beckhoff Automation GmbH & Co. KG4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8* 1. Redistributions of source code must retain the above copyright9* notice, this list of conditions and the following disclaimer.10* 2. Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND15* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE16* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE17* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE18* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL19* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS20* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)21* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT22* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY23* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF24* SUCH DAMAGE.25*/2627#include <sys/cdefs.h>2829#include <sys/param.h>30#include <sys/systm.h>31#include <sys/kernel.h>32#include <sys/module.h>33#include <sys/malloc.h>34#include <sys/bus.h>35#include <sys/cpu.h>36#include <machine/bus.h>37#include <sys/queue.h>3839#include <dev/ofw/openfirm.h>40#include <dev/ofw/ofw_bus.h>41#include <dev/ofw/ofw_bus_subr.h>4243#include <dev/clk/clk.h>44#include <dev/clk/clk_fixed.h>4546#include <dev/clk/xilinx/zynqmp_clk_mux.h>47#include <dev/clk/xilinx/zynqmp_clk_pll.h>48#include <dev/clk/xilinx/zynqmp_clk_fixed.h>49#include <dev/clk/xilinx/zynqmp_clk_div.h>50#include <dev/clk/xilinx/zynqmp_clk_gate.h>5152#include <dev/firmware/xilinx/pm_defs.h>5354#include "clkdev_if.h"55#include "zynqmp_firmware_if.h"5657#define ZYNQMP_MAX_NAME_LEN 1658#define ZYNQMP_MAX_NODES 659#define ZYNQMP_MAX_PARENTS 1006061#define ZYNQMP_CLK_IS_VALID (1 << 0)62#define ZYNQMP_CLK_IS_EXT (1 << 2)6364#define ZYNQMP_GET_NODE_TYPE(x) (x & 0x7)65#define ZYNQMP_GET_NODE_CLKFLAGS(x) ((x >> 8) & 0xFF)66#define ZYNQMP_GET_NODE_TYPEFLAGS(x) ((x >> 24) & 0xF)6768enum ZYNQMP_NODE_TYPE {69CLK_NODE_TYPE_NULL = 0,70CLK_NODE_TYPE_MUX,71CLK_NODE_TYPE_PLL,72CLK_NODE_TYPE_FIXED,73CLK_NODE_TYPE_DIV0,74CLK_NODE_TYPE_DIV1,75CLK_NODE_TYPE_GATE,76};7778/*79* Clock IDs in the firmware starts at 0 but80* exported clocks (and so clock exposed by the clock framework)81* starts at 182*/83#define ZYNQMP_ID_TO_CLK(x) ((x) + 1)84#define CLK_ID_TO_ZYNQMP(x) ((x) - 1)8586struct zynqmp_clk {87TAILQ_ENTRY(zynqmp_clk) next;88struct clknode_init_def clkdef;89uint32_t id;90uint32_t parentids[ZYNQMP_MAX_PARENTS];91uint32_t topology[ZYNQMP_MAX_NODES];92uint32_t attributes;93};9495struct zynqmp_clock_softc {96device_t dev;97device_t parent;98phandle_t node;99clk_t clk_pss_ref;100clk_t clk_video;101clk_t clk_pss_alt_ref;102clk_t clk_aux_ref;103clk_t clk_gt_crx_ref;104struct clkdom *clkdom;105};106107struct name_resp {108char name[16];109};110111struct zynqmp_clk_softc {112struct zynqmp_clk *clk;113device_t firmware;114uint32_t id;115};116117static int118zynqmp_clk_init(struct clknode *clk, device_t dev)119{120121clknode_init_parent_idx(clk, 0);122return (0);123}124125static clknode_method_t zynqmp_clk_clknode_methods[] = {126/* Device interface */127CLKNODEMETHOD(clknode_init, zynqmp_clk_init),128CLKNODEMETHOD_END129};130131DEFINE_CLASS_1(zynqmp_clk_clknode, zynqmp_clk_clknode_class,132zynqmp_clk_clknode_methods, sizeof(struct zynqmp_clk_softc), clknode_class);133134static int135zynqmp_clk_register(struct clkdom *clkdom, device_t fw, struct zynqmp_clk *clkdef)136{137struct clknode *clknode;138struct zynqmp_clk_softc *sc;139char *prev_clock_name = NULL;140char *clkname, *parent_name;141struct clknode_init_def *zynqclk;142int i;143144for (i = 0; i < ZYNQMP_MAX_NODES; i++) {145/* Bail early if we have no node */146if (ZYNQMP_GET_NODE_TYPE(clkdef->topology[i]) == CLK_NODE_TYPE_NULL)147break;148zynqclk = malloc(sizeof(*zynqclk), M_DEVBUF, M_WAITOK | M_ZERO);149zynqclk->id = clkdef->clkdef.id;150/* For the first node in the topology we use the main clock parents */151if (i == 0) {152zynqclk->parent_cnt = clkdef->clkdef.parent_cnt;153zynqclk->parent_names = clkdef->clkdef.parent_names;154} else {155zynqclk->parent_cnt = 1;156zynqclk->parent_names = malloc(sizeof(char *) * zynqclk->parent_cnt, M_DEVBUF, M_ZERO | M_WAITOK);157parent_name = strdup(prev_clock_name, M_DEVBUF);158zynqclk->parent_names[0] = (const char *)parent_name;159}160/* Register the clock node based on the topology type */161switch (ZYNQMP_GET_NODE_TYPE(clkdef->topology[i])) {162case CLK_NODE_TYPE_MUX:163asprintf(&clkname, M_DEVBUF, "%s_mux", clkdef->clkdef.name);164zynqclk->name = (const char *)clkname;165zynqmp_clk_mux_register(clkdom, fw, zynqclk);166break;167case CLK_NODE_TYPE_PLL:168asprintf(&clkname, M_DEVBUF, "%s_pll", clkdef->clkdef.name);169zynqclk->name = (const char *)clkname;170zynqmp_clk_pll_register(clkdom, fw, zynqclk);171break;172case CLK_NODE_TYPE_FIXED:173asprintf(&clkname, M_DEVBUF, "%s_fixed", clkdef->clkdef.name);174zynqclk->name = (const char *)clkname;175zynqmp_clk_fixed_register(clkdom, fw, zynqclk);176break;177case CLK_NODE_TYPE_DIV0:178asprintf(&clkname, M_DEVBUF, "%s_div0", clkdef->clkdef.name);179zynqclk->name = (const char *)clkname;180zynqmp_clk_div_register(clkdom, fw, zynqclk, CLK_DIV_TYPE_DIV0);181break;182case CLK_NODE_TYPE_DIV1:183asprintf(&clkname, M_DEVBUF, "%s_div1", clkdef->clkdef.name);184zynqclk->name = (const char *)clkname;185zynqmp_clk_div_register(clkdom, fw, zynqclk, CLK_DIV_TYPE_DIV1);186break;187case CLK_NODE_TYPE_GATE:188asprintf(&clkname, M_DEVBUF, "%s_gate", clkdef->clkdef.name);189zynqclk->name = (const char *)clkname;190zynqmp_clk_gate_register(clkdom, fw, zynqclk);191break;192case CLK_NODE_TYPE_NULL:193default:194clkname = NULL;195break;196}197if (i != 0) {198free(parent_name, M_DEVBUF);199free(zynqclk->parent_names, M_DEVBUF);200}201if (clkname != NULL)202prev_clock_name = strdup(clkname, M_DEVBUF);203free(clkname, M_DEVBUF);204free(zynqclk, M_DEVBUF);205}206207/* Register main clock */208clkdef->clkdef.name = clkdef->clkdef.name;209clkdef->clkdef.parent_cnt = 1;210clkdef->clkdef.parent_names = malloc(sizeof(char *) * clkdef->clkdef.parent_cnt, M_DEVBUF, M_ZERO | M_WAITOK);211clkdef->clkdef.parent_names[0] = strdup(prev_clock_name, M_DEVBUF);212clknode = clknode_create(clkdom, &zynqmp_clk_clknode_class, &clkdef->clkdef);213if (clknode == NULL)214return (1);215sc = clknode_get_softc(clknode);216sc->id = clkdef->clkdef.id - 1;217sc->firmware = fw;218sc->clk = clkdef;219clknode_register(clkdom, clknode);220return (0);221}222223static int224zynqmp_fw_clk_get_name(struct zynqmp_clock_softc *sc, struct zynqmp_clk *clk, uint32_t id)225{226char *clkname;227uint32_t query_data[4];228int rv;229230rv = ZYNQMP_FIRMWARE_QUERY_DATA(sc->parent, PM_QID_CLOCK_GET_NAME, id, 0, 0, query_data);231if (rv != 0)232return (rv);233if (query_data[0] == '\0')234return (EINVAL);235clkname = malloc(ZYNQMP_MAX_NAME_LEN, M_DEVBUF, M_ZERO | M_WAITOK);236memcpy(clkname, query_data, ZYNQMP_MAX_NAME_LEN);237clk->clkdef.name = clkname;238return (0);239}240241static int242zynqmp_fw_clk_get_attributes(struct zynqmp_clock_softc *sc, struct zynqmp_clk *clk, uint32_t id)243{244uint32_t query_data[4];245int rv;246247rv = ZYNQMP_FIRMWARE_QUERY_DATA(sc->parent, PM_QID_CLOCK_GET_ATTRIBUTES, id, 0, 0, query_data);248if (rv != 0)249return (rv);250clk->attributes = query_data[1];251return (0);252}253254static int255zynqmp_fw_clk_get_parents(struct zynqmp_clock_softc *sc, struct zynqmp_clk *clk, uint32_t id)256{257int rv, i;258uint32_t query_data[4];259260for (i = 0; i < ZYNQMP_MAX_PARENTS; i += 3) {261clk->parentids[i] = -1;262clk->parentids[i + 1] = -1;263clk->parentids[i + 2] = -1;264rv = ZYNQMP_FIRMWARE_QUERY_DATA(sc->parent, PM_QID_CLOCK_GET_PARENTS, id, i, 0, query_data);265clk->parentids[i] = query_data[1] & 0xFFFF;266clk->parentids[i + 1] = query_data[2] & 0xFFFF;267clk->parentids[i + 2] = query_data[3] & 0xFFFF;268if ((int32_t)query_data[1] == -1) {269clk->parentids[i] = -1;270break;271}272clk->parentids[i] += 1;273clk->clkdef.parent_cnt++;274if ((int32_t)query_data[2] == -1) {275clk->parentids[i + 1] = -1;276break;277}278clk->parentids[i + 1] += 1;279clk->clkdef.parent_cnt++;280if ((int32_t)query_data[3] == -1) {281clk->parentids[i + 2] = -1;282break;283}284clk->parentids[i + 2] += 1;285clk->clkdef.parent_cnt++;286if ((int32_t)query_data[1] == -2)287clk->parentids[i] = -2;288if ((int32_t)query_data[2] == -2)289clk->parentids[i + 1] = -2;290if ((int32_t)query_data[3] == -2)291clk->parentids[i + 2] = -2;292if (rv != 0)293break;294}295return (0);296}297298static int299zynqmp_fw_clk_get_topology(struct zynqmp_clock_softc *sc, struct zynqmp_clk *clk, uint32_t id)300{301uint32_t query_data[4];302int rv;303304rv = ZYNQMP_FIRMWARE_QUERY_DATA(sc->parent, PM_QID_CLOCK_GET_TOPOLOGY, id, 0, 0, query_data);305if (rv != 0)306return (rv);307clk->topology[0] = query_data[1];308clk->topology[1] = query_data[2];309clk->topology[2] = query_data[3];310if (query_data[3] == '\0')311goto out;312rv = ZYNQMP_FIRMWARE_QUERY_DATA(sc->parent, PM_QID_CLOCK_GET_TOPOLOGY, id, 3, 0, query_data);313if (rv != 0)314return (rv);315clk->topology[3] = query_data[1];316clk->topology[4] = query_data[2];317clk->topology[5] = query_data[3];318319out:320return (0);321}322323static int324zynqmp_clock_ofw_map(struct clkdom *clkdom, uint32_t ncells,325phandle_t *cells, struct clknode **clk)326{327328if (ncells != 1)329return (ERANGE);330*clk = clknode_find_by_id(clkdom, ZYNQMP_ID_TO_CLK(cells[0]));331if (*clk == NULL)332return (ENXIO);333return (0);334}335336static int337zynqmp_fw_clk_get_all(struct zynqmp_clock_softc *sc)338{339TAILQ_HEAD(tailhead, zynqmp_clk) clk_list;340struct zynqmp_clk *clk, *tmp, *tmp2;341char *clkname;342int rv, i;343uint32_t query_data[4], num_clock;344345TAILQ_INIT(&clk_list);346rv = ZYNQMP_FIRMWARE_QUERY_DATA(sc->parent,347PM_QID_CLOCK_GET_NUM_CLOCKS,3480,3490,3500,351query_data);352if (rv != 0) {353device_printf(sc->dev, "Cannot get clock details from the firmware\n");354return (ENXIO);355}356357num_clock = query_data[1];358for (i = 0; i < num_clock; i++) {359clk = malloc(sizeof(*clk), M_DEVBUF, M_WAITOK | M_ZERO);360clk->clkdef.id = ZYNQMP_ID_TO_CLK(i);361zynqmp_fw_clk_get_name(sc, clk, i);362zynqmp_fw_clk_get_attributes(sc, clk, i);363if ((clk->attributes & ZYNQMP_CLK_IS_VALID) == 0) {364free(clk, M_DEVBUF);365continue;366}367if (clk->attributes & ZYNQMP_CLK_IS_EXT)368goto skip_ext;369/* Get parents id */370rv = zynqmp_fw_clk_get_parents(sc, clk, i);371if (rv != 0) {372device_printf(sc->dev, "Cannot get parent for %s\n", clk->clkdef.name);373free(clk, M_DEVBUF);374continue;375}376/* Get topology */377rv = zynqmp_fw_clk_get_topology(sc, clk, i);378if (rv != 0) {379device_printf(sc->dev, "Cannot get topology for %s\n", clk->clkdef.name);380free(clk, M_DEVBUF);381continue;382}383skip_ext:384TAILQ_INSERT_TAIL(&clk_list, clk, next);385}386387/* Add a dummy clock */388clk = malloc(sizeof(*clk), M_DEVBUF, M_WAITOK | M_ZERO);389clkname = strdup("dummy", M_DEVBUF);390clk->clkdef.name = (const char *)clkname;391clk->clkdef.id = i;392clk->attributes = ZYNQMP_CLK_IS_EXT;393TAILQ_INSERT_TAIL(&clk_list, clk, next);394395/* Map parents id to name */396TAILQ_FOREACH_SAFE(clk, &clk_list, next, tmp) {397if (clk->attributes & ZYNQMP_CLK_IS_EXT)398continue;399clk->clkdef.parent_names = malloc(sizeof(char *) * clk->clkdef.parent_cnt, M_DEVBUF, M_ZERO | M_WAITOK);400for (i = 0; i < ZYNQMP_MAX_PARENTS; i++) {401if (clk->parentids[i] == -1)402break;403if (clk->parentids[i] == -2) {404clk->clkdef.parent_names[i] = strdup("dummy", M_DEVBUF);405continue;406}407TAILQ_FOREACH(tmp2, &clk_list, next) {408if (tmp2->clkdef.id == clk->parentids[i]) {409if (tmp2->attributes & ZYNQMP_CLK_IS_EXT) {410int idx;411412if (ofw_bus_find_string_index( sc->node,413"clock-names", tmp2->clkdef.name, &idx) == ENOENT)414clk->clkdef.parent_names[i] = strdup("dummy", M_DEVBUF);415else416clk->clkdef.parent_names[i] = strdup(tmp2->clkdef.name, M_DEVBUF);417}418else419clk->clkdef.parent_names[i] = strdup(tmp2->clkdef.name, M_DEVBUF);420break;421}422}423}424}425426sc->clkdom = clkdom_create(sc->dev);427if (sc->clkdom == NULL)428panic("Cannot create clkdom\n");429clkdom_set_ofw_mapper(sc->clkdom, zynqmp_clock_ofw_map);430431/* Register the clocks */432TAILQ_FOREACH_SAFE(clk, &clk_list, next, tmp) {433if (clk->attributes & ZYNQMP_CLK_IS_EXT) {434if (strcmp(clk->clkdef.name, "dummy") == 0) {435struct clk_fixed_def dummy;436437bzero(&dummy, sizeof(dummy));438dummy.clkdef.id = clk->clkdef.id;439dummy.clkdef.name = strdup("dummy", M_DEVBUF);440clknode_fixed_register(sc->clkdom, &dummy);441free(__DECONST(char *, dummy.clkdef.name), M_DEVBUF);442}443} else444zynqmp_clk_register(sc->clkdom, sc->parent, clk);445446TAILQ_REMOVE(&clk_list, clk, next);447for (i = 0; i < clk->clkdef.parent_cnt; i++)448free(__DECONST(char *, clk->clkdef.parent_names[i]), M_DEVBUF);449free(clk->clkdef.parent_names, M_DEVBUF);450free(__DECONST(char *, clk->clkdef.name), M_DEVBUF);451free(clk, M_DEVBUF);452}453454if (clkdom_finit(sc->clkdom) != 0)455panic("cannot finalize clkdom initialization\n");456457if (bootverbose)458clkdom_dump(sc->clkdom);459460return (0);461}462463static int464zynqmp_clock_probe(device_t dev)465{466467if (!ofw_bus_status_okay(dev))468return (ENXIO);469if (!ofw_bus_is_compatible(dev, "xlnx,zynqmp-clk"))470return (ENXIO);471device_set_desc(dev, "ZynqMP Clock Controller");472473return (BUS_PROBE_DEFAULT);474}475476static int477zynqmp_clock_attach(device_t dev)478{479struct zynqmp_clock_softc *sc;480int rv;481482sc = device_get_softc(dev);483sc->dev = dev;484sc->parent = device_get_parent(dev);485sc->node = ofw_bus_get_node(dev);486487/* Enable all clocks */488if (clk_get_by_ofw_name(dev, 0, "pss_ref_clk", &sc->clk_pss_ref) != 0) {489device_printf(dev, "Cannot get pss_ref_clk clock\n");490return (ENXIO);491}492rv = clk_enable(sc->clk_pss_ref);493if (rv != 0) {494device_printf(dev, "Could not enable clock pss_ref_clk\n");495return (ENXIO);496}497if (clk_get_by_ofw_name(dev, 0, "video_clk", &sc->clk_video) != 0) {498device_printf(dev, "Cannot get video_clk clock\n");499return (ENXIO);500}501rv = clk_enable(sc->clk_video);502if (rv != 0) {503device_printf(dev, "Could not enable clock video_clk\n");504return (ENXIO);505}506if (clk_get_by_ofw_name(dev, 0, "pss_alt_ref_clk", &sc->clk_pss_alt_ref) != 0) {507device_printf(dev, "Cannot get pss_alt_ref_clk clock\n");508return (ENXIO);509}510rv = clk_enable(sc->clk_pss_alt_ref);511if (rv != 0) {512device_printf(dev, "Could not enable clock pss_alt_ref_clk\n");513return (ENXIO);514}515if (clk_get_by_ofw_name(dev, 0, "aux_ref_clk", &sc->clk_aux_ref) != 0) {516device_printf(dev, "Cannot get pss_aux_clk clock\n");517return (ENXIO);518}519rv = clk_enable(sc->clk_aux_ref);520if (rv != 0) {521device_printf(dev, "Could not enable clock pss_aux_clk\n");522return (ENXIO);523}524if (clk_get_by_ofw_name(dev, 0, "gt_crx_ref_clk", &sc->clk_gt_crx_ref) != 0) {525device_printf(dev, "Cannot get gt_crx_ref_clk clock\n");526return (ENXIO);527}528rv = clk_enable(sc->clk_gt_crx_ref);529if (rv != 0) {530device_printf(dev, "Could not enable clock gt_crx_ref_clk\n");531return (ENXIO);532}533534rv = zynqmp_fw_clk_get_all(sc);535if (rv != 0) {536clk_disable(sc->clk_gt_crx_ref);537clk_disable(sc->clk_aux_ref);538clk_disable(sc->clk_pss_alt_ref);539clk_disable(sc->clk_video);540clk_disable(sc->clk_pss_ref);541return (rv);542}543return (0);544}545546static device_method_t zynqmp_clock_methods[] = {547/* device_if */548DEVMETHOD(device_probe, zynqmp_clock_probe),549DEVMETHOD(device_attach, zynqmp_clock_attach),550551DEVMETHOD_END552};553554static driver_t zynqmp_clock_driver = {555"zynqmp_clock",556zynqmp_clock_methods,557sizeof(struct zynqmp_clock_softc),558};559560EARLY_DRIVER_MODULE(zynqmp_clock, simplebus, zynqmp_clock_driver, 0, 0,561BUS_PASS_BUS + BUS_PASS_ORDER_LAST);562563564