178 lines
4.3 KiB
C
178 lines
4.3 KiB
C
|
/*
|
||
|
* 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");
|
||
|
|