M7350/kernel/drivers/of/of_spmi.c
2024-09-09 08:52:07 +00:00

462 lines
12 KiB
C

/* Copyright (c) 2012, The Linux Foundation. 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 version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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/spmi.h>
#include <linux/irq.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_spmi.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/types.h>
struct of_spmi_dev_info {
struct spmi_controller *ctrl;
struct spmi_boardinfo b_info;
};
struct of_spmi_res_info {
struct device_node *node;
uint32_t num_reg;
uint32_t num_irq;
};
/*
* Initialize r_info structure for safe usage
*/
static inline void of_spmi_init_resource(struct of_spmi_res_info *r_info,
struct device_node *node)
{
r_info->node = node;
r_info->num_reg = 0;
r_info->num_irq = 0;
}
/*
* Calculate the number of resources to allocate
*
* The caller is responsible for initializing the of_spmi_res_info structure.
*/
static void of_spmi_sum_resources(struct of_spmi_res_info *r_info,
bool has_reg)
{
struct of_irq oirq;
uint64_t size;
uint32_t flags;
int i = 0;
while (of_irq_map_one(r_info->node, i, &oirq) == 0)
i++;
r_info->num_irq += i;
if (!has_reg)
return;
/*
* We can't use of_address_to_resource here since it includes
* address translation; and address translation assumes that no
* parent buses have a size-cell of 0. But SPMI does have a
* size-cell of 0.
*/
i = 0;
while (of_get_address(r_info->node, i, &size, &flags) != NULL)
i++;
r_info->num_reg += i;
}
/*
* Allocate dev_node array for spmi_device - used with spmi-dev-container
*/
static inline int of_spmi_alloc_devnode_store(struct of_spmi_dev_info *d_info,
uint32_t num_dev_node)
{
d_info->b_info.num_dev_node = num_dev_node;
d_info->b_info.dev_node = kzalloc(sizeof(struct spmi_resource) *
num_dev_node, GFP_KERNEL);
if (!d_info->b_info.dev_node)
return -ENOMEM;
return 0;
}
/*
* Allocate enough memory to handle the resources associated with the
* primary node.
*/
static int of_spmi_allocate_node_resources(struct of_spmi_dev_info *d_info,
struct of_spmi_res_info *r_info)
{
uint32_t num_irq = r_info->num_irq, num_reg = r_info->num_reg;
struct resource *res = NULL;
if (num_irq || num_reg) {
res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
if (!res)
return -ENOMEM;
}
d_info->b_info.res.num_resources = num_reg + num_irq;
d_info->b_info.res.resource = res;
return 0;
}
/*
* Allocate enough memory to handle the resources associated with the
* spmi-dev-container nodes.
*/
static int of_spmi_allocate_devnode_resources(struct of_spmi_dev_info *d_info,
struct of_spmi_res_info *r_info,
uint32_t idx)
{
uint32_t num_irq = r_info->num_irq, num_reg = r_info->num_reg;
struct resource *res = NULL;
if (num_irq || num_reg) {
res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
if (!res)
return -ENOMEM;
}
d_info->b_info.dev_node[idx].num_resources = num_reg + num_irq;
d_info->b_info.dev_node[idx].resource = res;
return 0;
}
/*
* free node resources - used with primary node
*/
static void of_spmi_free_node_resources(struct of_spmi_dev_info *d_info)
{
kfree(d_info->b_info.res.resource);
}
/*
* free devnode resources - used with spmi-dev-container
*/
static void of_spmi_free_devnode_resources(struct of_spmi_dev_info *d_info)
{
int i;
for (i = 0; i < d_info->b_info.num_dev_node; i++)
kfree(d_info->b_info.dev_node[i].resource);
kfree(d_info->b_info.dev_node);
}
static void of_spmi_populate_resources(struct of_spmi_dev_info *d_info,
struct of_spmi_res_info *r_info,
struct resource *res)
{
uint32_t num_irq = r_info->num_irq, num_reg = r_info->num_reg;
int i;
const __be32 *addrp;
uint64_t size;
uint32_t flags;
if ((num_irq || num_reg) && (res != NULL)) {
for (i = 0; i < num_reg; i++, res++) {
/* Addresses are always 16 bits */
addrp = of_get_address(r_info->node, i, &size, &flags);
BUG_ON(!addrp);
res->start = be32_to_cpup(addrp);
res->end = res->start + size - 1;
res->flags = flags;
of_property_read_string_index(r_info->node, "reg-names",
i, &res->name);
}
WARN_ON(of_irq_to_resource_table(r_info->node, res, num_irq) !=
num_irq);
}
}
/*
* Gather primary node resources and populate.
*/
static void of_spmi_populate_node_resources(struct of_spmi_dev_info *d_info,
struct of_spmi_res_info *r_info)
{
struct resource *res;
res = d_info->b_info.res.resource;
d_info->b_info.res.of_node = r_info->node;
of_property_read_string(r_info->node, "label",
&d_info->b_info.res.label);
of_spmi_populate_resources(d_info, r_info, res);
}
/*
* Gather node devnode resources and populate - used with spmi-dev-container.
*/
static void of_spmi_populate_devnode_resources(struct of_spmi_dev_info *d_info,
struct of_spmi_res_info *r_info,
int idx)
{
struct resource *res;
res = d_info->b_info.dev_node[idx].resource;
d_info->b_info.dev_node[idx].of_node = r_info->node;
of_property_read_string(r_info->node, "label",
&d_info->b_info.dev_node[idx].label);
of_spmi_populate_resources(d_info, r_info, res);
}
/*
* create a single spmi_device
*/
static int of_spmi_create_device(struct of_spmi_dev_info *d_info,
struct device_node *node)
{
struct spmi_controller *ctrl = d_info->ctrl;
struct spmi_boardinfo *b_info = &d_info->b_info;
void *result;
int rc;
rc = of_modalias_node(node, b_info->name, sizeof(b_info->name));
if (rc < 0) {
dev_err(&ctrl->dev, "of_spmi modalias failure on %s\n",
node->full_name);
return rc;
}
b_info->of_node = of_node_get(node);
result = spmi_new_device(ctrl, b_info);
if (result == NULL) {
dev_err(&ctrl->dev, "of_spmi: Failure registering %s\n",
node->full_name);
of_node_put(node);
return -ENODEV;
}
return 0;
}
/*
* Walks all children of a node containing the spmi-dev-container
* binding. This special type of spmi_device can include resources
* from more than one device node.
*/
static void of_spmi_walk_dev_container(struct of_spmi_dev_info *d_info,
struct device_node *container)
{
struct of_spmi_res_info r_info = {};
struct spmi_controller *ctrl = d_info->ctrl;
struct device_node *node;
int rc, i, num_dev_node = 0;
if (!of_device_is_available(container))
return;
/*
* Count the total number of device_nodes so we know how much
* device_store to allocate.
*/
for_each_child_of_node(container, node) {
if (!of_device_is_available(node))
continue;
num_dev_node++;
}
rc = of_spmi_alloc_devnode_store(d_info, num_dev_node);
if (rc) {
dev_err(&ctrl->dev, "%s: unable to allocate devnode resources\n",
__func__);
return;
}
i = 0;
for_each_child_of_node(container, node) {
if (!of_device_is_available(node))
continue;
of_spmi_init_resource(&r_info, node);
of_spmi_sum_resources(&r_info, true);
rc = of_spmi_allocate_devnode_resources(d_info, &r_info, i);
if (rc) {
dev_err(&ctrl->dev, "%s: unable to allocate"
" resources\n", __func__);
of_spmi_free_devnode_resources(d_info);
return;
}
of_spmi_populate_devnode_resources(d_info, &r_info, i);
i++;
}
of_spmi_init_resource(&r_info, container);
of_spmi_sum_resources(&r_info, true);
rc = of_spmi_allocate_node_resources(d_info, &r_info);
if (rc) {
dev_err(&ctrl->dev, "%s: unable to allocate resources\n",
__func__);
of_spmi_free_node_resources(d_info);
}
of_spmi_populate_node_resources(d_info, &r_info);
rc = of_spmi_create_device(d_info, container);
if (rc) {
dev_err(&ctrl->dev, "%s: unable to create device for"
" node %s\n", __func__, container->full_name);
of_spmi_free_devnode_resources(d_info);
return;
}
}
/*
* Walks all children of a node containing the spmi-slave-container
* binding. This indicates that all spmi_devices created from this
* point all share the same slave_id.
*/
static void of_spmi_walk_slave_container(struct of_spmi_dev_info *d_info,
struct device_node *container)
{
struct spmi_controller *ctrl = d_info->ctrl;
struct device_node *node;
int rc;
for_each_child_of_node(container, node) {
struct of_spmi_res_info r_info;
if (!of_device_is_available(node))
continue;
/**
* Check to see if this node contains children which
* should be all created as the same spmi_device.
*/
if (of_get_property(node, "spmi-dev-container", NULL)) {
of_spmi_walk_dev_container(d_info, node);
continue;
}
of_spmi_init_resource(&r_info, node);
of_spmi_sum_resources(&r_info, true);
rc = of_spmi_allocate_node_resources(d_info, &r_info);
if (rc) {
dev_err(&ctrl->dev, "%s: unable to allocate"
" resources\n", __func__);
goto slave_err;
}
of_spmi_populate_node_resources(d_info, &r_info);
rc = of_spmi_create_device(d_info, node);
if (rc) {
dev_err(&ctrl->dev, "%s: unable to create device for"
" node %s\n", __func__, node->full_name);
goto slave_err;
}
}
return;
slave_err:
of_spmi_free_node_resources(d_info);
}
int of_spmi_register_devices(struct spmi_controller *ctrl)
{
struct device_node *node = ctrl->dev.of_node;
/* Only register child devices if the ctrl has a node pointer set */
if (!node)
return -ENODEV;
if (of_get_property(node, "spmi-slave-container", NULL)) {
dev_err(&ctrl->dev, "%s: structural error: spmi-slave-container"
" is prohibited at the root level\n", __func__);
return -EINVAL;
} else if (of_get_property(node, "spmi-dev-container", NULL)) {
dev_err(&ctrl->dev, "%s: structural error: spmi-dev-container"
" is prohibited at the root level\n", __func__);
return -EINVAL;
}
/**
* Make best effort to launch as many nodes as possible. If there are
* syntax errors, we will simply ignore that subtree and keep going.
*/
for_each_child_of_node(ctrl->dev.of_node, node) {
struct of_spmi_dev_info d_info = {};
const __be32 *slave_id;
int len, rc, have_dev_container = 0;
slave_id = of_get_property(node, "reg", &len);
if (!slave_id) {
dev_err(&ctrl->dev, "%s: invalid sid "
"on %s\n", __func__, node->full_name);
continue;
}
d_info.b_info.slave_id = be32_to_cpup(slave_id);
d_info.ctrl = ctrl;
if (of_get_property(node, "spmi-dev-container", NULL))
have_dev_container = 1;
if (of_get_property(node, "spmi-slave-container", NULL)) {
if (have_dev_container)
of_spmi_walk_dev_container(&d_info, node);
else
of_spmi_walk_slave_container(&d_info, node);
} else {
struct of_spmi_res_info r_info;
/**
* A dev container at the second level without a slave
* container is considered an error.
*/
if (have_dev_container) {
dev_err(&ctrl->dev, "%s: structural error,"
" node %s has spmi-dev-container without"
" specifying spmi-slave-container\n",
__func__, node->full_name);
continue;
}
if (!of_device_is_available(node))
continue;
of_spmi_init_resource(&r_info, node);
of_spmi_sum_resources(&r_info, false);
rc = of_spmi_allocate_node_resources(&d_info, &r_info);
if (rc) {
dev_err(&ctrl->dev, "%s: unable to allocate"
" resources\n", __func__);
of_spmi_free_node_resources(&d_info);
continue;
}
of_spmi_populate_node_resources(&d_info, &r_info);
rc = of_spmi_create_device(&d_info, node);
if (rc) {
dev_err(&ctrl->dev, "%s: unable to create"
" device\n", __func__);
of_spmi_free_node_resources(&d_info);
continue;
}
}
}
return 0;
}
EXPORT_SYMBOL(of_spmi_register_devices);
MODULE_LICENSE("GPL v2");