852 lines
22 KiB
C
852 lines
22 KiB
C
/* Copyright (c) 2013-2015, 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/err.h>
|
|
#include <linux/irqdomain.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/pinctrl/pinctrl.h>
|
|
#include <linux/pinctrl/pinmux.h>
|
|
#include <linux/pinctrl/machine.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include "core.h"
|
|
#include "pinconf.h"
|
|
#include "pinctrl-msm.h"
|
|
|
|
/**
|
|
* struct msm_pinctrl_dd: represents the pinctrol driver data.
|
|
* @base: virtual base of TLMM.
|
|
* @irq: interrupt number for TLMM summary interrupt.
|
|
* @num_pins: Number of total pins present on TLMM.
|
|
* @msm_pindesc: list of descriptors for each pin.
|
|
* @num_pintypes: number of pintypes on TLMM.
|
|
* @msm_pintype: points to the representation of all pin types supported.
|
|
* @pctl: pin controller instance managed by the driver.
|
|
* @pctl_dev: pin controller descriptor registered with the pinctrl subsystem.
|
|
* @pin_grps: list of pin groups available to the driver.
|
|
* @num_grps: number of groups.
|
|
* @pmx_funcs:list of pin functions available to the driver
|
|
* @num_funcs: number of functions.
|
|
* @dev: pin contol device.
|
|
*/
|
|
struct msm_pinctrl_dd {
|
|
void __iomem *base;
|
|
int irq;
|
|
unsigned int num_pins;
|
|
struct msm_pindesc *msm_pindesc;
|
|
unsigned int num_pintypes;
|
|
struct msm_pintype_info *msm_pintype;
|
|
struct pinctrl_desc pctl;
|
|
struct pinctrl_dev *pctl_dev;
|
|
struct msm_pin_grps *pin_grps;
|
|
unsigned int num_grps;
|
|
struct msm_pmx_funcs *pmx_funcs;
|
|
unsigned int num_funcs;
|
|
struct device *dev;
|
|
};
|
|
|
|
/**
|
|
* struct msm_irq_of_info: represents of init data for tlmm interrupt
|
|
* controllers
|
|
* @compat: compat string for tlmm interrup controller instance.
|
|
* @irq_init: irq chip initialization callback.
|
|
*/
|
|
struct msm_irq_of_info {
|
|
const char *compat;
|
|
int (*irq_init)(struct device_node *np, struct irq_chip *ic);
|
|
};
|
|
|
|
static int msm_pmx_functions_count(struct pinctrl_dev *pctldev)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
|
|
dd = pinctrl_dev_get_drvdata(pctldev);
|
|
return dd->num_funcs;
|
|
}
|
|
|
|
static const char *msm_pmx_get_fname(struct pinctrl_dev *pctldev,
|
|
unsigned selector)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
|
|
dd = pinctrl_dev_get_drvdata(pctldev);
|
|
return dd->pmx_funcs[selector].name;
|
|
}
|
|
|
|
static int msm_pmx_get_groups(struct pinctrl_dev *pctldev,
|
|
unsigned selector, const char * const **groups,
|
|
unsigned * const num_groups)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
|
|
dd = pinctrl_dev_get_drvdata(pctldev);
|
|
*groups = dd->pmx_funcs[selector].gps;
|
|
*num_groups = dd->pmx_funcs[selector].num_grps;
|
|
return 0;
|
|
}
|
|
|
|
static void msm_pmx_prg_fn(struct pinctrl_dev *pctldev, unsigned selector,
|
|
unsigned group, bool enable)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
const unsigned int *pins;
|
|
struct msm_pindesc *pindesc;
|
|
struct msm_pintype_info *pinfo;
|
|
unsigned int pin, cnt, func;
|
|
|
|
dd = pinctrl_dev_get_drvdata(pctldev);
|
|
pins = dd->pin_grps[group].pins;
|
|
pindesc = dd->msm_pindesc;
|
|
|
|
/*
|
|
* for each pin in the pin group selected, program the correspoding
|
|
* pin function number in the config register.
|
|
*/
|
|
for (cnt = 0; cnt < dd->pin_grps[group].num_pins; cnt++) {
|
|
pin = pins[cnt];
|
|
pinfo = pindesc[pin].pin_info;
|
|
pin = pin - pinfo->pin_start;
|
|
func = dd->pin_grps[group].func;
|
|
pinfo->prg_func(pin, func, enable, pinfo);
|
|
}
|
|
}
|
|
|
|
static int msm_pmx_enable(struct pinctrl_dev *pctldev, unsigned selector,
|
|
unsigned group)
|
|
{
|
|
msm_pmx_prg_fn(pctldev, selector, group, true);
|
|
return 0;
|
|
}
|
|
|
|
static void msm_pmx_disable(struct pinctrl_dev *pctldev,
|
|
unsigned selector, unsigned group)
|
|
{
|
|
msm_pmx_prg_fn(pctldev, selector, group, false);
|
|
}
|
|
|
|
/* Enable gpio function for a pin */
|
|
static int msm_pmx_gpio_request(struct pinctrl_dev *pctldev,
|
|
struct pinctrl_gpio_range *grange,
|
|
unsigned pin)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
struct msm_pindesc *pindesc;
|
|
struct msm_pintype_info *pinfo;
|
|
|
|
dd = pinctrl_dev_get_drvdata(pctldev);
|
|
pindesc = dd->msm_pindesc;
|
|
pinfo = pindesc[pin].pin_info;
|
|
/* All TLMM versions use function 0 for gpio function */
|
|
pinfo->prg_func(pin, 0, true, pinfo);
|
|
return 0;
|
|
}
|
|
|
|
static struct pinmux_ops msm_pmxops = {
|
|
.get_functions_count = msm_pmx_functions_count,
|
|
.get_function_name = msm_pmx_get_fname,
|
|
.get_function_groups = msm_pmx_get_groups,
|
|
.enable = msm_pmx_enable,
|
|
.disable = msm_pmx_disable,
|
|
.gpio_request_enable = msm_pmx_gpio_request,
|
|
};
|
|
|
|
static int msm_pconf_prg(struct pinctrl_dev *pctldev, unsigned int pin,
|
|
unsigned long *config, bool rw)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
struct msm_pindesc *pindesc;
|
|
struct msm_pintype_info *pinfo;
|
|
|
|
dd = pinctrl_dev_get_drvdata(pctldev);
|
|
pindesc = dd->msm_pindesc;
|
|
pinfo = pindesc[pin].pin_info;
|
|
pin = pin - pinfo->pin_start;
|
|
return pinfo->prg_cfg(pin, config, rw, pinfo);
|
|
}
|
|
|
|
static int msm_pconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
|
|
unsigned long config)
|
|
{
|
|
return msm_pconf_prg(pctldev, pin, &config, true);
|
|
}
|
|
|
|
static int msm_pconf_get(struct pinctrl_dev *pctldev, unsigned int pin,
|
|
unsigned long *config)
|
|
{
|
|
return msm_pconf_prg(pctldev, pin, config, false);
|
|
}
|
|
|
|
static int msm_pconf_group_set(struct pinctrl_dev *pctldev,
|
|
unsigned group, unsigned long config)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
const unsigned int *pins;
|
|
unsigned int cnt;
|
|
|
|
dd = pinctrl_dev_get_drvdata(pctldev);
|
|
pins = dd->pin_grps[group].pins;
|
|
|
|
for (cnt = 0; cnt < dd->pin_grps[group].num_pins; cnt++)
|
|
msm_pconf_set(pctldev, pins[cnt], config);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_pconf_group_get(struct pinctrl_dev *pctldev,
|
|
unsigned int group, unsigned long *config)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
const unsigned int *pins;
|
|
|
|
dd = pinctrl_dev_get_drvdata(pctldev);
|
|
pins = dd->pin_grps[group].pins;
|
|
msm_pconf_get(pctldev, pins[0], config);
|
|
return 0;
|
|
}
|
|
|
|
static struct pinconf_ops msm_pconfops = {
|
|
.pin_config_get = msm_pconf_get,
|
|
.pin_config_set = msm_pconf_set,
|
|
.pin_config_group_get = msm_pconf_group_get,
|
|
.pin_config_group_set = msm_pconf_group_set,
|
|
};
|
|
|
|
static int msm_get_grps_count(struct pinctrl_dev *pctldev)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
|
|
dd = pinctrl_dev_get_drvdata(pctldev);
|
|
return dd->num_grps;
|
|
}
|
|
|
|
static const char *msm_get_grps_name(struct pinctrl_dev *pctldev,
|
|
unsigned selector)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
|
|
dd = pinctrl_dev_get_drvdata(pctldev);
|
|
return dd->pin_grps[selector].name;
|
|
}
|
|
|
|
static int msm_get_grps_pins(struct pinctrl_dev *pctldev,
|
|
unsigned selector, const unsigned **pins, unsigned *num_pins)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
|
|
dd = pinctrl_dev_get_drvdata(pctldev);
|
|
*pins = dd->pin_grps[selector].pins;
|
|
*num_pins = dd->pin_grps[selector].num_pins;
|
|
return 0;
|
|
}
|
|
|
|
static struct msm_pintype_info *msm_pgrp_to_pintype(struct device_node *nd,
|
|
struct msm_pinctrl_dd *dd)
|
|
{
|
|
struct device_node *ptype_nd;
|
|
struct msm_pintype_info *pinfo = NULL;
|
|
int idx = 0;
|
|
|
|
/*Extract pin type node from parent node */
|
|
ptype_nd = of_parse_phandle(nd, "qcom,pins", 0);
|
|
/* find the pin type info for this pin type node */
|
|
for (idx = 0; idx < dd->num_pintypes; idx++) {
|
|
pinfo = &dd->msm_pintype[idx];
|
|
if (ptype_nd == pinfo->node) {
|
|
of_node_put(ptype_nd);
|
|
break;
|
|
}
|
|
}
|
|
return pinfo;
|
|
}
|
|
|
|
/* create pinctrl_map entries by parsing device tree nodes */
|
|
static int msm_dt_node_to_map(struct pinctrl_dev *pctldev,
|
|
struct device_node *cfg_np, struct pinctrl_map **maps,
|
|
unsigned *nmaps)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
struct device_node *parent;
|
|
struct msm_pindesc *pindesc;
|
|
struct msm_pintype_info *pinfo;
|
|
struct pinctrl_map *map;
|
|
const char *grp_name;
|
|
char *fn_name;
|
|
u32 val;
|
|
unsigned long *cfg;
|
|
unsigned int fn_name_len = 0;
|
|
int cfg_cnt = 0, map_cnt = 0, func_cnt = 0, ret = 0;
|
|
|
|
dd = pinctrl_dev_get_drvdata(pctldev);
|
|
pindesc = dd->msm_pindesc;
|
|
/* get parent node of config node */
|
|
parent = of_get_parent(cfg_np);
|
|
/*
|
|
* parent node contains pin grouping
|
|
* get pin type from pin grouping
|
|
*/
|
|
pinfo = msm_pgrp_to_pintype(parent, dd);
|
|
/* check if there is a function associated with the parent pin group */
|
|
if (of_find_property(parent, "qcom,pin-func", NULL))
|
|
func_cnt++;
|
|
/* get pin configs */
|
|
ret = pinconf_generic_parse_dt_config(cfg_np, &cfg, &cfg_cnt);
|
|
if (ret) {
|
|
dev_err(dd->dev, "properties incorrect\n");
|
|
return ret;
|
|
}
|
|
|
|
map_cnt = cfg_cnt + func_cnt;
|
|
|
|
/* Allocate memory for pin-map entries */
|
|
map = kzalloc(sizeof(*map) * map_cnt, GFP_KERNEL);
|
|
if (!map) {
|
|
kfree(cfg);
|
|
return -ENOMEM;
|
|
}
|
|
*nmaps = 0;
|
|
|
|
/* Get group name from node */
|
|
of_property_read_string(parent, "label", &grp_name);
|
|
/* create the config map entry */
|
|
map[*nmaps].data.configs.group_or_pin = grp_name;
|
|
map[*nmaps].data.configs.configs = cfg;
|
|
map[*nmaps].data.configs.num_configs = cfg_cnt;
|
|
map[*nmaps].type = PIN_MAP_TYPE_CONFIGS_GROUP;
|
|
*nmaps += 1;
|
|
|
|
/* If there is no function specified in device tree return */
|
|
if (func_cnt == 0) {
|
|
*maps = map;
|
|
goto no_func;
|
|
}
|
|
/* Get function mapping */
|
|
of_property_read_u32(parent, "qcom,pin-func", &val);
|
|
|
|
fn_name_len = strlen(grp_name) + strlen("-func") + 1;
|
|
fn_name = kzalloc(fn_name_len, GFP_KERNEL);
|
|
if (!fn_name) {
|
|
ret = -ENOMEM;
|
|
goto func_err;
|
|
}
|
|
snprintf(fn_name, fn_name_len, "%s-func", grp_name);
|
|
map[*nmaps].data.mux.group = grp_name;
|
|
map[*nmaps].data.mux.function = fn_name;
|
|
map[*nmaps].type = PIN_MAP_TYPE_MUX_GROUP;
|
|
*nmaps += 1;
|
|
*maps = map;
|
|
of_node_put(parent);
|
|
return 0;
|
|
|
|
func_err:
|
|
kfree(cfg);
|
|
kfree(map);
|
|
no_func:
|
|
of_node_put(parent);
|
|
return ret;
|
|
}
|
|
|
|
/* free the memory allocated to hold the pin-map table */
|
|
static void msm_dt_free_map(struct pinctrl_dev *pctldev,
|
|
struct pinctrl_map *map, unsigned num_maps)
|
|
{
|
|
int idx;
|
|
|
|
for (idx = 0; idx < num_maps; idx++) {
|
|
if (map[idx].type == PIN_MAP_TYPE_CONFIGS_GROUP)
|
|
kfree(map[idx].data.configs.configs);
|
|
else if (map[idx].type == PIN_MAP_TYPE_MUX_GROUP)
|
|
kfree(map[idx].data.mux.function);
|
|
};
|
|
|
|
kfree(map);
|
|
}
|
|
|
|
static struct pinctrl_ops msm_pctrlops = {
|
|
.get_groups_count = msm_get_grps_count,
|
|
.get_group_name = msm_get_grps_name,
|
|
.get_group_pins = msm_get_grps_pins,
|
|
.dt_node_to_map = msm_dt_node_to_map,
|
|
.dt_free_map = msm_dt_free_map,
|
|
};
|
|
|
|
static int msm_pinctrl_request_gpio(struct gpio_chip *gc, unsigned offset)
|
|
{
|
|
return pinctrl_request_gpio(gc->base + offset);
|
|
}
|
|
|
|
static void msm_pinctrl_free_gpio(struct gpio_chip *gc, unsigned offset)
|
|
{
|
|
pinctrl_free_gpio(gc->base + offset);
|
|
}
|
|
|
|
static int msm_of_get_pin(struct device_node *np, int index,
|
|
struct msm_pinctrl_dd *dd, uint *pin)
|
|
{
|
|
struct of_phandle_args pargs;
|
|
struct msm_pintype_info *pinfo, *pintype;
|
|
int num_pintypes;
|
|
int ret, i;
|
|
|
|
ret = of_parse_phandle_with_args(np, "qcom,pins", "#qcom,pin-cells",
|
|
index, &pargs);
|
|
if (ret)
|
|
return ret;
|
|
pintype = dd->msm_pintype;
|
|
num_pintypes = dd->num_pintypes;
|
|
for (i = 0; i < num_pintypes; i++) {
|
|
pinfo = &pintype[i];
|
|
/* Find the matching pin type node */
|
|
if (pargs.np != pinfo->node)
|
|
continue;
|
|
/* Check if arg specified is in valid range for pin type */
|
|
if (pargs.args[0] > pinfo->num_pins) {
|
|
ret = -EINVAL;
|
|
dev_err(dd->dev, "Invalid pin number for type %s\n",
|
|
pinfo->name);
|
|
goto out;
|
|
}
|
|
/*
|
|
* Pin number = index within pin type + start of pin numbers
|
|
* for this pin type
|
|
*/
|
|
*pin = pargs.args[0] + pinfo->pin_start;
|
|
}
|
|
out:
|
|
of_node_put(pargs.np);
|
|
return ret;
|
|
}
|
|
|
|
static int msm_pinctrl_dt_parse_pins(struct device_node *dev_node,
|
|
struct msm_pinctrl_dd *dd)
|
|
{
|
|
struct device *dev;
|
|
struct device_node *pgrp_np;
|
|
struct msm_pin_grps *pin_grps, *curr_grp;
|
|
struct msm_pmx_funcs *pmx_funcs, *curr_func;
|
|
char *func_name;
|
|
const char *grp_name;
|
|
int ret, i, grp_index = 0, func_index = 0;
|
|
uint pin = 0, *pins, num_grps = 0, num_pins = 0, len = 0;
|
|
uint num_funcs = 0;
|
|
u32 func = 0;
|
|
|
|
dev = dd->dev;
|
|
for_each_child_of_node(dev_node, pgrp_np) {
|
|
if (!of_find_property(pgrp_np, "qcom,pins", NULL))
|
|
continue;
|
|
if (of_find_property(pgrp_np, "qcom,pin-func", NULL))
|
|
num_funcs++;
|
|
num_grps++;
|
|
}
|
|
|
|
pin_grps = (struct msm_pin_grps *)devm_kzalloc(dd->dev,
|
|
sizeof(*pin_grps) * num_grps,
|
|
GFP_KERNEL);
|
|
if (!pin_grps) {
|
|
dev_err(dev, "Failed to allocate grp desc\n");
|
|
return -ENOMEM;
|
|
}
|
|
pmx_funcs = (struct msm_pmx_funcs *)devm_kzalloc(dd->dev,
|
|
sizeof(*pmx_funcs) * num_funcs,
|
|
GFP_KERNEL);
|
|
if (!pmx_funcs) {
|
|
dev_err(dev, "Failed to allocate grp desc\n");
|
|
return -ENOMEM;
|
|
}
|
|
/*
|
|
* Iterate over all child nodes, and for nodes containing pin lists
|
|
* populate corresponding pin group, and if provided, corresponding
|
|
* function
|
|
*/
|
|
for_each_child_of_node(dev_node, pgrp_np) {
|
|
if (!of_find_property(pgrp_np, "qcom,pins", NULL))
|
|
continue;
|
|
curr_grp = pin_grps + grp_index;
|
|
/* Get group name from label*/
|
|
ret = of_property_read_string(pgrp_np, "label", &grp_name);
|
|
if (ret) {
|
|
dev_err(dev, "Unable to allocate group name\n");
|
|
return ret;
|
|
}
|
|
ret = of_property_read_u32(pgrp_np, "qcom,num-grp-pins",
|
|
&num_pins);
|
|
if (ret) {
|
|
dev_err(dev, "pin count not specified for groups %s\n",
|
|
grp_name);
|
|
return ret;
|
|
}
|
|
pins = devm_kzalloc(dd->dev, sizeof(unsigned int) * num_pins,
|
|
GFP_KERNEL);
|
|
if (!pins) {
|
|
dev_err(dev, "Unable to allocte pins for %s\n",
|
|
grp_name);
|
|
return -ENOMEM;
|
|
}
|
|
for (i = 0; i < num_pins; i++) {
|
|
ret = msm_of_get_pin(pgrp_np, i, dd, &pin);
|
|
if (ret) {
|
|
dev_err(dev, "Pin grp %s does not have pins\n",
|
|
grp_name);
|
|
return ret;
|
|
}
|
|
pins[i] = pin;
|
|
}
|
|
curr_grp->pins = pins;
|
|
curr_grp->num_pins = num_pins;
|
|
curr_grp->name = grp_name;
|
|
grp_index++;
|
|
/* Check if func specified */
|
|
if (!of_find_property(pgrp_np, "qcom,pin-func", NULL))
|
|
continue;
|
|
curr_func = pmx_funcs + func_index;
|
|
len = strlen(grp_name) + strlen("-func") + 1;
|
|
func_name = devm_kzalloc(dev, len, GFP_KERNEL);
|
|
if (!func_name) {
|
|
dev_err(dev, "Cannot allocate func name for grp %s",
|
|
grp_name);
|
|
return -ENOMEM;
|
|
}
|
|
snprintf(func_name, len, "%s%s", grp_name, "-func");
|
|
curr_func->name = func_name;
|
|
curr_func->gps = devm_kzalloc(dev, sizeof(char *), GFP_KERNEL);
|
|
if (!curr_func->gps) {
|
|
dev_err(dev, "failed to alloc memory for group list ");
|
|
return -ENOMEM;
|
|
}
|
|
of_property_read_u32(pgrp_np, "qcom,pin-func", &func);
|
|
curr_grp->func = func;
|
|
curr_func->gps[0] = grp_name;
|
|
curr_func->num_grps = 1;
|
|
func_index++;
|
|
}
|
|
dd->pin_grps = pin_grps;
|
|
dd->num_grps = num_grps;
|
|
dd->pmx_funcs = pmx_funcs;
|
|
dd->num_funcs = num_funcs;
|
|
return 0;
|
|
}
|
|
|
|
static void msm_populate_pindesc(struct msm_pintype_info *pinfo,
|
|
struct msm_pindesc *msm_pindesc)
|
|
{
|
|
int i;
|
|
struct msm_pindesc *pindesc;
|
|
|
|
for (i = 0; i < pinfo->num_pins; i++) {
|
|
pindesc = &msm_pindesc[i + pinfo->pin_start];
|
|
pindesc->pin_info = pinfo;
|
|
snprintf(pindesc->name, sizeof(pindesc->name),
|
|
"%s-%d", pinfo->name, i);
|
|
}
|
|
}
|
|
|
|
static bool msm_pintype_supports_gpio(struct msm_pintype_info *pinfo)
|
|
{
|
|
struct device_node *pt_node;
|
|
|
|
if (!pinfo->node)
|
|
return false;
|
|
|
|
for_each_child_of_node(pinfo->node, pt_node) {
|
|
if (of_find_property(pt_node, "gpio-controller", NULL)) {
|
|
pinfo->gc.of_node = pt_node;
|
|
pinfo->supports_gpio = true;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool msm_pintype_supports_irq(struct msm_pintype_info *pinfo)
|
|
{
|
|
struct device_node *pt_node;
|
|
|
|
if (!pinfo->node)
|
|
return false;
|
|
|
|
for_each_child_of_node(pinfo->node, pt_node) {
|
|
if (of_find_property(pt_node, "interrupt-controller", NULL)) {
|
|
pinfo->irq_chip->node = pt_node;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int msm_pinctrl_dt_parse_pintype(struct device_node *dev_node,
|
|
struct msm_pinctrl_dd *dd)
|
|
{
|
|
struct device_node *pt_node;
|
|
struct msm_pindesc *msm_pindesc;
|
|
struct msm_pintype_info *pintype, *pinfo;
|
|
u32 num_pins, pinfo_entries, curr_pins;
|
|
int i, ret;
|
|
uint total_pins = 0;
|
|
|
|
pinfo = dd->msm_pintype;
|
|
pinfo_entries = dd->num_pintypes;
|
|
curr_pins = 0;
|
|
|
|
for_each_child_of_node(dev_node, pt_node) {
|
|
for (i = 0; i < pinfo_entries; i++) {
|
|
pintype = &pinfo[i];
|
|
if (strcmp(pt_node->name, pintype->name))
|
|
continue;
|
|
of_node_get(pt_node);
|
|
pintype->node = pt_node;
|
|
/* determine number of pins of given pin type */
|
|
ret = of_property_read_u32(pt_node, "qcom,num-pins",
|
|
&num_pins);
|
|
if (ret) {
|
|
dev_err(dd->dev, "num pins not specified\n");
|
|
goto fail;
|
|
}
|
|
/* determine pin number range for given pin type */
|
|
pintype->num_pins = num_pins;
|
|
pintype->pin_start = curr_pins;
|
|
pintype->pin_end = curr_pins + num_pins;
|
|
pintype->set_reg_base(dd->base, pintype);
|
|
total_pins += num_pins;
|
|
curr_pins += num_pins;
|
|
}
|
|
}
|
|
dd->msm_pindesc = devm_kzalloc(dd->dev,
|
|
sizeof(struct msm_pindesc) *
|
|
total_pins, GFP_KERNEL);
|
|
if (!dd->msm_pindesc) {
|
|
dev_err(dd->dev, "Unable to allocate msm pindesc");
|
|
goto fail;
|
|
}
|
|
|
|
dd->num_pins = total_pins;
|
|
msm_pindesc = dd->msm_pindesc;
|
|
/*
|
|
* Populate pin descriptor based on each pin type present in Device
|
|
* tree and supported by the driver
|
|
*/
|
|
for (i = 0; i < pinfo_entries; i++) {
|
|
pintype = &pinfo[i];
|
|
/* If entry not in device tree, skip */
|
|
if (!pintype->node)
|
|
continue;
|
|
msm_populate_pindesc(pintype, msm_pindesc);
|
|
}
|
|
return 0;
|
|
fail:
|
|
for (i = 0; i < pinfo_entries; i++) {
|
|
pintype = &pinfo[i];
|
|
if (pintype->node)
|
|
of_node_put(pintype->node);
|
|
}
|
|
return -ENOMEM;
|
|
}
|
|
|
|
|
|
static void msm_pinctrl_cleanup_dd(struct msm_pinctrl_dd *dd)
|
|
{
|
|
int i;
|
|
struct msm_pintype_info *pintype;
|
|
|
|
pintype = dd->msm_pintype;
|
|
for (i = 0; i < dd->num_pintypes; i++) {
|
|
if (pintype->node)
|
|
of_node_put(dd->msm_pintype[i].node);
|
|
}
|
|
}
|
|
|
|
static int msm_pinctrl_get_drvdata(struct msm_pinctrl_dd *dd,
|
|
struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
struct device_node *node = pdev->dev.of_node;
|
|
|
|
ret = msm_pinctrl_dt_parse_pintype(node, dd);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = msm_pinctrl_dt_parse_pins(node, dd);
|
|
if (ret)
|
|
msm_pinctrl_cleanup_dd(dd);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int msm_register_pinctrl(struct msm_pinctrl_dd *dd)
|
|
{
|
|
int i;
|
|
struct pinctrl_pin_desc *pindesc;
|
|
struct msm_pintype_info *pinfo, *pintype;
|
|
struct pinctrl_desc *ctrl_desc = &dd->pctl;
|
|
|
|
ctrl_desc->name = "msm-pinctrl";
|
|
ctrl_desc->owner = THIS_MODULE;
|
|
ctrl_desc->pmxops = &msm_pmxops;
|
|
ctrl_desc->confops = &msm_pconfops;
|
|
ctrl_desc->pctlops = &msm_pctrlops;
|
|
|
|
pindesc = devm_kzalloc(dd->dev, sizeof(*pindesc) * dd->num_pins,
|
|
GFP_KERNEL);
|
|
if (!pindesc) {
|
|
dev_err(dd->dev, "Failed to allocate pinctrl pin desc\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < dd->num_pins; i++) {
|
|
pindesc[i].number = i;
|
|
pindesc[i].name = dd->msm_pindesc[i].name;
|
|
}
|
|
ctrl_desc->pins = pindesc;
|
|
ctrl_desc->npins = dd->num_pins;
|
|
dd->pctl_dev = pinctrl_register(ctrl_desc, dd->dev, dd);
|
|
if (!dd->pctl_dev) {
|
|
dev_err(dd->dev, "could not register pinctrl driver\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pinfo = dd->msm_pintype;
|
|
for (i = 0; i < dd->num_pintypes; i++) {
|
|
pintype = &pinfo[i];
|
|
if (!pintype->supports_gpio)
|
|
continue;
|
|
pintype->grange.name = pintype->name;
|
|
pintype->grange.id = i;
|
|
pintype->grange.pin_base = pintype->pin_start;
|
|
pintype->grange.base = pintype->gc.base;
|
|
pintype->grange.npins = pintype->gc.ngpio;
|
|
pintype->grange.gc = &pintype->gc;
|
|
pinctrl_add_gpio_range(dd->pctl_dev, &pintype->grange);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void msm_register_gpiochip(struct msm_pinctrl_dd *dd)
|
|
{
|
|
|
|
struct gpio_chip *gc;
|
|
struct msm_pintype_info *pintype, *pinfo;
|
|
int i, ret = 0;
|
|
|
|
pinfo = dd->msm_pintype;
|
|
for (i = 0; i < dd->num_pintypes; i++) {
|
|
pintype = &pinfo[i];
|
|
if (!msm_pintype_supports_gpio(pintype))
|
|
continue;
|
|
gc = &pintype->gc;
|
|
gc->request = msm_pinctrl_request_gpio;
|
|
gc->free = msm_pinctrl_free_gpio;
|
|
gc->dev = dd->dev;
|
|
gc->ngpio = pintype->num_pins;
|
|
gc->base = -1;
|
|
ret = gpiochip_add(gc);
|
|
if (ret) {
|
|
dev_err(dd->dev, "failed to register gpio chip\n");
|
|
pinfo->supports_gpio = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int msm_register_irqchip(struct msm_pinctrl_dd *dd)
|
|
{
|
|
struct msm_pintype_info *pintype, *pinfo;
|
|
int i, ret = 0;
|
|
|
|
pinfo = dd->msm_pintype;
|
|
for (i = 0; i < dd->num_pintypes; i++) {
|
|
pintype = &pinfo[i];
|
|
if (!msm_pintype_supports_irq(pintype))
|
|
continue;
|
|
ret = pintype->init_irq(dd->irq, pintype, dd->dev);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int msm_pinctrl_probe(struct platform_device *pdev,
|
|
struct msm_tlmm_desc *tlmm_info)
|
|
{
|
|
struct msm_pinctrl_dd *dd;
|
|
struct device *dev = &pdev->dev;
|
|
int ret;
|
|
|
|
dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL);
|
|
if (!dd) {
|
|
dev_err(dev, "Alloction failed for driver data\n");
|
|
return -ENOMEM;
|
|
}
|
|
dd->dev = dev;
|
|
dd->msm_pintype = tlmm_info->pintypes;
|
|
dd->base = tlmm_info->base;
|
|
dd->irq = tlmm_info->irq;
|
|
dd->num_pintypes = tlmm_info->num_pintypes;
|
|
ret = msm_pinctrl_get_drvdata(dd, pdev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "driver data not available\n");
|
|
return ret;
|
|
}
|
|
msm_register_gpiochip(dd);
|
|
ret = msm_register_pinctrl(dd);
|
|
if (ret) {
|
|
msm_pinctrl_cleanup_dd(dd);
|
|
return ret;
|
|
}
|
|
msm_register_irqchip(dd);
|
|
platform_set_drvdata(pdev, dd);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(msm_pinctrl_probe);
|
|
|
|
#ifdef CONFIG_USE_PINCTRL_IRQ
|
|
struct irq_chip mpm_tlmm_irq_extn = {
|
|
.irq_eoi = NULL,
|
|
.irq_mask = NULL,
|
|
.irq_unmask = NULL,
|
|
.irq_retrigger = NULL,
|
|
.irq_set_type = NULL,
|
|
.irq_set_wake = NULL,
|
|
.irq_disable = NULL,
|
|
};
|
|
|
|
struct msm_irq_of_info msm_tlmm_irq[] = {
|
|
#ifdef CONFIG_PINCTRL_MSM_TLMM
|
|
{
|
|
.compat = "qcom,msm-tlmm-gp",
|
|
.irq_init = msm_tlmm_of_gp_irq_init,
|
|
},
|
|
#endif
|
|
};
|
|
|
|
int __init msm_tlmm_of_irq_init(struct device_node *controller,
|
|
struct device_node *parent)
|
|
{
|
|
int rc, i;
|
|
const char *compat;
|
|
|
|
rc = of_property_read_string(controller, "compatible", &compat);
|
|
if (rc)
|
|
return rc;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(msm_tlmm_irq); i++) {
|
|
struct msm_irq_of_info *tlmm_info = &msm_tlmm_irq[i];
|
|
|
|
if (!of_compat_cmp(tlmm_info->compat, compat, strlen(compat)))
|
|
return tlmm_info->irq_init(controller,
|
|
&mpm_tlmm_irq_extn);
|
|
|
|
}
|
|
return -EIO;
|
|
}
|
|
#endif
|