tegrakernel/kernel/nvidia/drivers/platform/tegra/tegra_prod.c

621 lines
14 KiB
C
Raw Permalink Normal View History

2022-02-16 09:13:02 -06:00
/*
* Copyright (c) 2014-2017, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/io.h>
#include <linux/tegra_prod.h>
#define PROD_TUPLE_NUM (sizeof(struct prod_tuple)/sizeof(u32))
/* tegra_prod: Tegra Prod list for the given submodule
* @n_prod_cells: Number of prod setting cells.
*/
struct tegra_prod {
struct tegra_prod_config *prod_config;
int num; /* number of tegra_prod*/
int n_prod_cells;
};
struct prod_tuple {
u32 index; /* Address base index */
u32 addr; /* offset address*/
u32 mask; /* mask */
u32 val; /* value */
};
struct tegra_prod_config {
const char *name;
struct prod_tuple *prod_tuple;
int count; /* number of prod_tuple*/
bool boot_init;
};
static int tegra_prod_get_child_tupple_count(struct device *dev,
struct device_node *np,
int n_tupple)
{
struct device_node *child;
int count;
int total_tupple = 0;
count = of_property_count_u32_elems(np, "prod");
if (count > 0) {
if ((count < n_tupple) || (count % n_tupple != 0)) {
dev_err(dev, "Node %s has invalid entries\n", np->name);
return -EINVAL;
}
total_tupple = count / n_tupple;
}
for_each_available_child_of_node(np, child) {
count = tegra_prod_get_child_tupple_count(dev, child, n_tupple);
if (count < 0)
return count;
total_tupple += count;
}
return total_tupple;
}
static int tegra_prod_read_prod_data(struct device *dev,
struct device_node *np,
struct prod_tuple *p_tuple,
int n_tupple)
{
u32 pval;
int count;
int t_count;
int cnt;
int index;
int ret;
count = of_property_count_u32_elems(np, "prod");
if (count <= 0) {
dev_dbg(dev, "Node %s: prod prop not found\n", np->name);
return 0;
}
t_count = count / n_tupple;
for (cnt = 0; cnt < t_count; cnt++, p_tuple++) {
index = cnt * n_tupple;
if (n_tupple == 4) {
ret = of_property_read_u32_index(np, "prod", index,
&pval);
if (ret) {
dev_err(dev, "Failed to parse prod of node %s\n",
np->name);
return -EINVAL;
}
p_tuple->index = pval;
index++;
} else {
p_tuple->index = 0;
}
ret = of_property_read_u32_index(np, "prod", index, &pval);
if (ret) {
dev_err(dev, "Failed to parse address of node %s\n",
np->name);
return -EINVAL;
}
p_tuple->addr = pval;
index++;
ret = of_property_read_u32_index(np, "prod", index, &pval);
if (ret) {
dev_err(dev, "Failed to parse mask of node %s\n",
np->name);
return -EINVAL;
}
p_tuple->mask = pval;
index++;
ret = of_property_read_u32_index(np, "prod", index, &pval);
if (ret) {
dev_err(dev, "Failed to parse value of node %s\n",
np->name);
return -EINVAL;
}
p_tuple->val = pval;
}
return t_count;
}
static int tegra_prod_read_node_tupple(struct device *dev,
struct device_node *np,
struct prod_tuple *p_tuple,
int n_tupple)
{
int ret = 0;
int sindex;
struct device_node *child;
ret = tegra_prod_read_prod_data(dev, np, p_tuple, n_tupple);
if (ret < 0)
return -EINVAL;
sindex = ret;
p_tuple += ret;
for_each_available_child_of_node(np, child) {
ret = tegra_prod_read_node_tupple(dev, child,
p_tuple, n_tupple);
if (ret < 0)
return -EINVAL;
sindex += ret;
p_tuple += ret;
}
return sindex;
}
/* Process the tupples and optimise for the register configuration for
* Same location.
*/
static void tegra_prod_optimise_tupple(struct prod_tuple *p_tuple,
int n_tupple)
{
struct prod_tuple *ti, *tj;
u32 mask;
int i, j;
for (i = 0; i < n_tupple; ++i) {
ti = p_tuple + i;
for (j = i + 1; j < n_tupple; ++j) {
tj = p_tuple + j;
if (ti->index != tj->index)
continue;
if (ti->addr != tj->addr)
continue;
mask = ti->mask & tj->mask;
if (!mask)
continue;
ti->val &= ~mask;
ti->mask &= ~mask;
}
}
}
/**
* tegra_prod_parse_dt - Read the prod setting form Device tree.
* @np: device node from which the property value is to be read.
* @np_prod: Prod setting node.
* @tegra_prod: The list of tegra prods.
*
* Read the prod setting form DT according the prod name in tegra prod list.
* prod tuple will be allocated dynamically according to the tuple number of
* each prod in DT.
*
* Returns 0 on success.
*/
static int tegra_prod_parse_dt(struct device *dev,
const struct device_node *np,
const struct device_node *np_prod,
struct tegra_prod *tegra_prod)
{
struct device_node *child;
struct tegra_prod_config *t_prod;
struct prod_tuple *p_tuple;
int n_child;
int n_tupple = 3;
int ret;
int count;
u32 pval;
bool mask_opt;
if (!tegra_prod || !tegra_prod->prod_config) {
dev_err(dev, "Node %s: Invalid tegra prods list.\n", np->name);
return -EINVAL;
};
mask_opt = of_property_read_bool(np_prod, "enable-mask-optimisation");
ret = of_property_read_u32(np_prod, "#prod-cells", &pval);
if (!ret)
n_tupple = pval;
if ((n_tupple != 3) && (n_tupple != 4)) {
dev_err(dev, "Node %s: Prod cells not supported\n", np->name);
return -EINVAL;
}
tegra_prod->n_prod_cells = n_tupple;
n_child = 0;
for_each_available_child_of_node(np_prod, child) {
t_prod = &tegra_prod->prod_config[n_child];
t_prod->name = child->name;
count = tegra_prod_get_child_tupple_count(dev, child, n_tupple);
if (count < 0) {
dev_err(dev, "Node %s: Child has not proper setting\n",
child->name);
return -EINVAL;
}
if (!count) {
dev_err(dev, "Node %s: prod prop not found\n",
child->name);
return -EINVAL;
}
t_prod->count = count;
t_prod->prod_tuple = devm_kcalloc(dev, t_prod->count,
sizeof(*p_tuple), GFP_KERNEL);
if (!t_prod->prod_tuple)
return -ENOMEM;
t_prod->boot_init = of_property_read_bool(child,
"nvidia,prod-boot-init");
ret = tegra_prod_read_node_tupple(dev, child,
t_prod->prod_tuple, n_tupple);
if (ret < 0) {
dev_err(dev, "Node %s: Reading prod setting failed: %d\n",
child->name, ret);
return ret;
}
if (t_prod->count != ret) {
dev_err(dev, "Node %s: prod read failed: exp %d read %d\n",
child->name, t_prod->count, ret);
return -EINVAL;
}
/* Optimise the prod configuration */
if (mask_opt)
tegra_prod_optimise_tupple(t_prod->prod_tuple,
t_prod->count);
n_child++;
}
tegra_prod->num = n_child;
return 0;
}
/**
* tegra_prod_set_tuple - Only set a tuple.
* @base: base address of the register.
* @prod_tuple: the tuple to set.
* @new_mask: Mask override value, 0 means use from tupple.
*
* Returns 0 on success.
*/
static int tegra_prod_set_tuple(void __iomem **base,
struct prod_tuple *prod_tuple,
u32 new_mask)
{
u32 reg;
u32 mask = (new_mask) ? new_mask : prod_tuple->mask;
if (!prod_tuple)
return -EINVAL;
reg = readl(base[prod_tuple->index] + prod_tuple->addr);
reg = ((reg & ~mask) | (prod_tuple->val & mask));
writel(reg, base[prod_tuple->index] + prod_tuple->addr);
return 0;
}
/**
* tegra_prod_set - Set one prod setting.
* @base: base address of the register.
* @tegra_prod: the prod setting to set.
*
* Set all the tuples in one tegra_prod.
* Returns 0 on success.
*/
static int tegra_prod_set(void __iomem **base,
struct tegra_prod_config *tegra_prod)
{
int i;
int ret;
if (!tegra_prod)
return -EINVAL;
for (i = 0; i < tegra_prod->count; i++) {
ret = tegra_prod_set_tuple(base, &tegra_prod->prod_tuple[i], 0);
if (ret)
return ret;
}
return 0;
}
/**
* tegra_prod_set_list - Set all the prod settings of the list in sequence.
* @base: base address of the register.
* @tegra_prod: the list of tegra prods.
*
* Returns 0 on success.
*/
int tegra_prod_set_list(void __iomem **base,
struct tegra_prod *tegra_prod)
{
int i;
int ret;
if (!tegra_prod)
return -EINVAL;
for (i = 0; i < tegra_prod->num; i++) {
ret = tegra_prod_set(base, &tegra_prod->prod_config[i]);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL(tegra_prod_set_list);
/**
* tegra_prod_set_boot_init - Set all the prod settings of the list in sequence
* Which are needed for boot initialisation.
* @base: base address of the register.
* @tegra_prod: the list of tegra prods.
*
* Returns 0 on success.
*/
int tegra_prod_set_boot_init(void __iomem **base,
struct tegra_prod *tegra_prod)
{
int i;
int ret;
if (!tegra_prod)
return -EINVAL;
for (i = 0; i < tegra_prod->num; i++) {
if (!tegra_prod->prod_config[i].boot_init)
continue;
ret = tegra_prod_set(base, &tegra_prod->prod_config[i]);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL(tegra_prod_set_boot_init);
/**
* tegra_prod_set_by_name - Set the prod setting according the name.
* @base: base address of the register.
* @name: the name of tegra prod need to set.
* @tegra_prod: the list of tegra prods.
*
* Find the tegra prod in the list according to the name. Then set
* that tegra prod.
*
* Returns 0 on success.
*/
int tegra_prod_set_by_name(void __iomem **base, const char *name,
struct tegra_prod *tegra_prod)
{
int i;
struct tegra_prod_config *t_prod;
if (!tegra_prod)
return -EINVAL;
for (i = 0; i < tegra_prod->num; i++) {
t_prod = &tegra_prod->prod_config[i];
if (!strcasecmp(t_prod->name, name))
return tegra_prod_set(base, t_prod);
}
return -ENODEV;
}
EXPORT_SYMBOL(tegra_prod_set_by_name);
/**
* tegra_prod_set_by_name_partially - Set the prod setting from list partially
* under given prod name. The matching is done
* qith index, offset and mask.
* @base: base address of the register.
* @name: the name of tegra prod need to set.
* @tegra_prod: the list of tegra prods.
* @index: Index of base address.
* @offset: Offset of the register.
* @mask: Mask field on given register.
*
* Find the tegra prod in the list according to the name. Then set
* that tegra prod which has matching of index, offset and mask.
*
* Returns 0 on success.
*/
int tegra_prod_set_by_name_partially(void __iomem **base, const char *name,
struct tegra_prod *tegra_prod, u32 index,
u32 offset, u32 mask)
{
struct tegra_prod_config *t_prod;
int ret;
int i;
bool found = false;
if (!tegra_prod)
return -EINVAL;
for (i = 0; i < tegra_prod->num; i++) {
t_prod = &tegra_prod->prod_config[i];
if (!strcasecmp(t_prod->name, name)) {
found = true;
break;
}
}
if (!found)
return -ENODEV;
for (i = 0; i < t_prod->count; i++) {
struct prod_tuple *ptuple = &t_prod->prod_tuple[i];;
if ((ptuple->index != index) || (ptuple->addr != offset) ||
((ptuple->mask & mask) != mask))
continue;
ret = tegra_prod_set_tuple(base, ptuple, mask);
if (ret < 0)
return ret;
}
return 0;
}
EXPORT_SYMBOL(tegra_prod_set_by_name_partially);
bool tegra_prod_by_name_supported(struct tegra_prod *tegra_prod,
const char *name)
{
int i;
struct tegra_prod_config *t_prod;
if (!tegra_prod)
return false;
for (i = 0; i < tegra_prod->num; i++) {
t_prod = &tegra_prod->prod_config[i];
if (!t_prod)
break;
if (!strcasecmp(t_prod->name, name))
return true;
}
return false;
}
EXPORT_SYMBOL(tegra_prod_by_name_supported);
/**
* tegra_prod_init - Init tegra prod list.
# @dev: Device handle.
* @np: device node from which the property value is to be read.
*
* Query all the prod settings under DT node & Init the tegra prod list
* automatically.
*
* Returns 0 on success, -EINVAL for wrong prod number, -ENOMEM if faild
* to allocate memory for tegra prod list.
*/
static struct tegra_prod *tegra_prod_init(struct device *dev,
const struct device_node *np)
{
struct tegra_prod *tegra_prod;
struct device_node *np_prod;
int prod_num = 0;
int ret;
np_prod = of_get_child_by_name(np, "prod-settings");
if (!np_prod)
return ERR_PTR(-ENODEV);
/* Check whether child is enabled or not */
if (!of_device_is_available(np_prod)) {
dev_err(dev, "Node %s: Node is not enabled\n", np_prod->name);
return ERR_PTR(-ENODEV);
}
prod_num = of_get_child_count(np_prod);
if (prod_num <= 0) {
dev_err(dev, "Node %s: No child node for prod settings\n",
np_prod->name);
return ERR_PTR(-ENODEV);
}
tegra_prod = devm_kzalloc(dev, sizeof(*tegra_prod), GFP_KERNEL);
if (!tegra_prod)
return ERR_PTR(-ENOMEM);
tegra_prod->prod_config = devm_kcalloc(dev, prod_num,
sizeof(*tegra_prod->prod_config),
GFP_KERNEL);
if (!tegra_prod->prod_config)
return ERR_PTR(-ENOMEM);
tegra_prod->num = prod_num;
ret = tegra_prod_parse_dt(dev, np, np_prod, tegra_prod);
if (ret) {
dev_err(dev, "Node %s: Faild to read the Prod Setting.\n",
np->name);
return ERR_PTR(ret);
}
return tegra_prod;
}
static void devm_tegra_prod_release(struct device *dev, void *res)
{
}
struct tegra_prod *devm_tegra_prod_get(struct device *dev)
{
struct tegra_prod **ptr, *prod_list;
ptr = devres_alloc(devm_tegra_prod_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
prod_list = tegra_prod_init(dev, dev->of_node);
if (IS_ERR(prod_list)) {
devres_free(ptr);
return prod_list;
}
*ptr = prod_list;
devres_add(dev, ptr);
return prod_list;
}
EXPORT_SYMBOL(devm_tegra_prod_get);
struct tegra_prod *devm_tegra_prod_get_from_node(struct device *dev,
struct device_node *np)
{
struct tegra_prod **ptr, *prod_list;
ptr = devres_alloc(devm_tegra_prod_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
prod_list = tegra_prod_init(dev, np);
if (IS_ERR(prod_list)) {
devres_free(ptr);
return prod_list;
}
*ptr = prod_list;
devres_add(dev, ptr);
return prod_list;
}
EXPORT_SYMBOL(devm_tegra_prod_get_from_node);