blob: 489383384a98309bfa0d140b3b303296ba1477c7 [file] [log] [blame]
/*
* Extended support for CS35L41 Amp
*
* Copyright 2017 Cirrus Logic
*
* Author: David Rhodes <david.rhodes@cirrus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/mfd/cs35l41/registers.h>
#include <linux/mfd/cs35l41/calibration.h>
#include <linux/mfd/cs35l41/big_data.h>
#include <linux/mfd/cs35l41/power.h>
#define CIRRUS_AMP_CLASS_NAME "cirrus"
struct class *cirrus_amp_class;
static int cirrus_amp_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *amp_node;
const char **mfd_suffixes;
const char **bd_suffixes;
int ret, num_amps, i, j;
if (np) {
ret = of_property_read_u32(np, "cirrus,num-amps", &num_amps);
if (ret < 0) {
dev_err(&pdev->dev,
"cirrus_amp: failed to parse num-amps\n");
return -EINVAL;
}
mfd_suffixes = kzalloc(num_amps * sizeof(char *), GFP_KERNEL);
if (mfd_suffixes == NULL) {
dev_err(&pdev->dev,
"Failed to allocate\n");
return -ENOMEM;
}
bd_suffixes = kzalloc(num_amps * sizeof(char *), GFP_KERNEL);
if (bd_suffixes == NULL) {
dev_err(&pdev->dev,
"Failed to allocate\n");
return -ENOMEM;
}
for (i = 0; i < num_amps; i++) {
amp_node = of_parse_phandle(np, "cirrus,amps", i);
dev_dbg(&pdev->dev, "Found linked amp: %s\n",
amp_node->full_name);
ret = of_property_read_string(amp_node,
"cirrus,mfd-suffix",
&mfd_suffixes[i]);
if (ret < 0)
dev_err(&pdev->dev,
"No MFD suffix found for amp: %s\n",
amp_node->full_name);
ret = of_property_read_string(amp_node,
"cirrus,bd-suffix",
&bd_suffixes[i]);
if (ret < 0)
dev_dbg(&pdev->dev,
"No BD suffix found for amp: %s\n",
amp_node->full_name);
of_node_put(amp_node);
}
for (i = 0; i < num_amps; i++) {
for (j = 0; j < num_amps; j++) {
if (mfd_suffixes[i] == NULL ||
mfd_suffixes[j] == NULL) {
dev_err(&pdev->dev,
"MFD suffixes incomplete\n");
kfree(mfd_suffixes);
return -EINVAL;
}
if (strcmp(mfd_suffixes[i],
mfd_suffixes[j]) == 0 &&
i != j) {
dev_err(&pdev->dev,
"MFD suffixes must be unique\n");
dev_err(&pdev->dev,
"Found duplicate suffix: %s\n",
mfd_suffixes[i]);
kfree(mfd_suffixes);
return -EINVAL;
}
/*bd_suffixes can be empty but must be unique*/
if (bd_suffixes[i] && bd_suffixes[j]) {
if (strcmp(bd_suffixes[i],
bd_suffixes[j]) == 0 &&
i != j) {
dev_err(&pdev->dev,
"BD suffixes must be unique\n");
dev_err(&pdev->dev,
"Found duplicate suffix: %s\n",
bd_suffixes[i]);
kfree(bd_suffixes);
kfree(mfd_suffixes);
return -EINVAL;
}
}
}
dev_info(&pdev->dev,
"Found MFD suffix: %s\n", mfd_suffixes[i]);
if (bd_suffixes[i])
dev_info(&pdev->dev,
"Found BD suffix: %s\n", bd_suffixes[i]);
}
} else {
dev_err(&pdev->dev,
"cirrus_amp: failed to parse cirrus-amp DT\n");
return -EINVAL;
}
cirrus_amp_class = class_create(THIS_MODULE,
CIRRUS_AMP_CLASS_NAME);
if (IS_ERR(cirrus_amp_class)) {
dev_err(&pdev->dev, "Failed to register cirrus amp class\n");
return -EINVAL;
}
cirrus_cal_init(cirrus_amp_class, num_amps, mfd_suffixes);
cirrus_bd_init(cirrus_amp_class, num_amps, mfd_suffixes, bd_suffixes);
cirrus_pwr_init(cirrus_amp_class, num_amps, mfd_suffixes);
kfree(mfd_suffixes);
return 0;
}
static int cirrus_amp_remove(struct platform_device *pdev)
{
cirrus_cal_exit();
cirrus_bd_exit();
cirrus_pwr_exit();
return 0;
}
static const struct of_device_id simple_of_match[] = {
{ .compatible = "cirrus-amp", },
{},
};
MODULE_DEVICE_TABLE(of, simple_of_match);
static struct platform_driver cirrus_amp = {
.driver = {
.name = "cirrus-amp",
.of_match_table = simple_of_match,
},
.probe = cirrus_amp_probe,
.remove = cirrus_amp_remove,
};
module_platform_driver(cirrus_amp);
MODULE_AUTHOR("David Rhodes <David.Rhodes@cirrus.com>");
MODULE_DESCRIPTION("Cirrus Amp driver");
MODULE_LICENSE("GPL");