blob: 16ce2f32792eb05b119dfe4b3d63f7cb01085468 [file]
/*
* drivers/amlogic/clk/sc2/sc2_secure_clk_mux.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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.
*
* This program is distributed in the hope that 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.
*
*/
#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/arm-smccc.h>
#include "../clkc.h"
#include "../clk-secure.h"
/*
* DOC: basic adjustable multiplexer clock that cannot gate
*
* Traits of this clock:
* prepare - clk_prepare only ensures that parents are prepared
* enable - clk_enable only ensures that parents are enabled
* rate - rate is only affected by parent switching. No clk_set_rate support
* parent - parent is adjustable through clk_set_parent
*/
static u8 sc2_clk_mux_get_parent(struct clk_hw *hw)
{
struct clk_mux *mux = to_clk_mux(hw);
int num_parents = clk_hw_get_num_parents(hw);
struct arm_smccc_res res;
u64 val;
if (!strcmp(clk_hw_get_name(hw), "cpu_clk"))
arm_smccc_smc(CPUCLK_SECURE_RW, CLK_CPU_REG_RW,
0, 0, 0, 0, 0, 0, &res);
else if (!strcmp(clk_hw_get_name(hw), "cpu1_clk") ||
!strcmp(clk_hw_get_name(hw), "cpu2_clk") ||
!strcmp(clk_hw_get_name(hw), "cpu3_clk") ||
!strcmp(clk_hw_get_name(hw), "dsu_clk"))
arm_smccc_smc(CPUCLK_SECURE_RW, CLK_CPU_DSU_REG_RW,
0, 0, 0, 0, 0, 0, &res);
else
arm_smccc_smc(CPUCLK_SECURE_RW, CLK_DSU_REG_RW,
0, 0, 0, 0, 0, 0, &res);
val = res.a0;
val >>= mux->shift;
val &= mux->mask;
if (mux->table) {
int i;
for (i = 0; i < num_parents; i++)
if (mux->table[i] == val)
return i;
return -EINVAL;
}
if (val && (mux->flags & CLK_MUX_INDEX_BIT))
val = ffs(val) - 1;
if (val && (mux->flags & CLK_MUX_INDEX_ONE))
val--;
if (val >= num_parents)
return -EINVAL;
return val;
}
static int sc2_clk_mux_set_parent(struct clk_hw *hw, u8 index)
{
struct clk_mux *mux = to_clk_mux(hw);
unsigned long flags = 0;
struct arm_smccc_res res;
if (mux->table) {
index = mux->table[index];
} else {
if (mux->flags & CLK_MUX_INDEX_BIT)
index = 1 << index;
if (mux->flags & CLK_MUX_INDEX_ONE)
index++;
}
if (mux->lock)
spin_lock_irqsave(mux->lock, flags);
else
__acquire(mux->lock);
if (!strcmp(clk_hw_get_name(hw), "cpu_clk"))
arm_smccc_smc(CPUCLK_SECURE_RW, SET_CPU0_MUX_PARENT,
index, mux->mask, mux->shift, 0, 0, 0, &res);
else if (!strcmp(clk_hw_get_name(hw), "cpu1_clk") ||
!strcmp(clk_hw_get_name(hw), "cpu2_clk") ||
!strcmp(clk_hw_get_name(hw), "cpu3_clk") ||
!strcmp(clk_hw_get_name(hw), "dsu_clk"))
arm_smccc_smc(CPUCLK_SECURE_RW, SET_CPU123_DSU_MUX_PARENT,
index, mux->mask, mux->shift, 0, 0, 0, &res);
else
arm_smccc_smc(CPUCLK_SECURE_RW, SET_DSU_PRE_MUX_PARENT,
index, mux->mask, mux->shift, 0, 0, 0, &res);
if (mux->lock)
spin_unlock_irqrestore(mux->lock, flags);
else
__release(mux->lock);
return 0;
}
const struct clk_ops sc2_clk_mux_ops = {
.get_parent = sc2_clk_mux_get_parent,
.set_parent = sc2_clk_mux_set_parent,
.determine_rate = __clk_mux_determine_rate,
};
EXPORT_SYMBOL_GPL(sc2_clk_mux_ops);
const struct clk_ops sc2_clk_mux_ro_ops = {
.get_parent = sc2_clk_mux_get_parent,
};
EXPORT_SYMBOL_GPL(sc2_clk_mux_ro_ops);