mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
5b9119c59f
PD#SWPL-69666 Problem: porting irblaster driver Solution: porting irblaster driver Verify: s4d Change-Id: If15957c9c4072bb08bf5454cc3e81eac646eb1ad Signed-off-by: wanwei.jiang <wanwei.jiang@amlogic.com>
355 lines
7.7 KiB
C
355 lines
7.7 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/err.h>
|
|
#include <linux/device.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/amlogic/irblaster.h>
|
|
|
|
void irblaster_chip_data_clear(struct irblaster_chip *chip)
|
|
{
|
|
chip->buffer = NULL;
|
|
chip->buffer_len = 0;
|
|
chip->sum_time = 0;
|
|
}
|
|
|
|
/**
|
|
* irblaster_send() - send raw level data
|
|
* @chip: irblaster controller
|
|
* @data: raw level data (us)
|
|
* @len: raw len
|
|
*/
|
|
int irblaster_send(struct irblaster_chip *chip, unsigned int *data,
|
|
unsigned int len)
|
|
{
|
|
unsigned int sum_time = 0;
|
|
int err, i;
|
|
|
|
if (!chip || len == 0 || len > MAX_PLUSE) {
|
|
pr_err("%s(): parameter error\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < len; i++)
|
|
sum_time += data[i];
|
|
|
|
chip->buffer = data;
|
|
chip->buffer_len = len;
|
|
chip->sum_time = sum_time;
|
|
|
|
if (chip->ops->send) {
|
|
err = chip->ops->send(chip, data, len);
|
|
if (err)
|
|
return err;
|
|
} else {
|
|
pr_err("%s(): irblaster func %s not found\n",
|
|
__func__, __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
irblaster_chip_data_clear(chip);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* irblaster_set_freq() - set irblaster freq
|
|
* @chip: irblaster controller
|
|
* @freq: irblaster freq (HZ)
|
|
*/
|
|
int irblaster_set_freq(struct irblaster_chip *chip, unsigned int freq)
|
|
{
|
|
int ret;
|
|
|
|
if (!chip || freq <= 0)
|
|
return -EINVAL;
|
|
|
|
if (chip->ops->set_freq) {
|
|
ret = chip->ops->set_freq(chip, freq);
|
|
if (ret)
|
|
return -EINVAL;
|
|
}
|
|
|
|
chip->state.freq = freq;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* irblaster_get_freq() - get irblaster freq
|
|
* @chip: irblaster controller
|
|
*/
|
|
unsigned int irblaster_get_freq(struct irblaster_chip *chip)
|
|
{
|
|
unsigned int freq;
|
|
|
|
if (!chip)
|
|
return -EINVAL;
|
|
|
|
if (chip->ops->get_freq) {
|
|
freq = chip->ops->get_freq(chip);
|
|
if (freq == 0)
|
|
return -EINVAL;
|
|
} else {
|
|
freq = chip->state.freq;
|
|
}
|
|
|
|
return freq;
|
|
}
|
|
|
|
/**
|
|
* irblaster_set_duty() - set irblaster duty
|
|
* @chip: irblaster controller
|
|
* @duty: irblaster duty
|
|
*/
|
|
int irblaster_set_duty(struct irblaster_chip *chip, unsigned int duty)
|
|
{
|
|
int ret;
|
|
|
|
if (!chip || duty <= 0 || duty > 100)
|
|
return -EINVAL;
|
|
|
|
if (chip->ops->set_duty) {
|
|
ret = chip->ops->set_duty(chip, duty);
|
|
if (ret)
|
|
return -EINVAL;
|
|
}
|
|
|
|
chip->state.duty = duty;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* irblaster_set_duty() - set irblaster duty
|
|
* @chip: irblaster controller
|
|
* @duty: irblaster duty
|
|
*/
|
|
unsigned int irblaster_get_duty(struct irblaster_chip *chip)
|
|
{
|
|
unsigned int duty;
|
|
|
|
if (chip->ops->get_duty) {
|
|
duty = chip->ops->get_duty(chip);
|
|
if (duty == 0)
|
|
return -EINVAL;
|
|
} else {
|
|
duty = chip->state.duty;
|
|
}
|
|
|
|
return duty;
|
|
}
|
|
|
|
/**
|
|
* irblasterchip_remove() - remove a irblaster Controller
|
|
* @chip: the irblaster chip to remove
|
|
* @Returns: 0 on success or a negative error code on failure.
|
|
*/
|
|
int irblasterchip_remove(struct irblaster_chip *chip)
|
|
{
|
|
mutex_lock(&irblaster_lock);
|
|
list_del_init(&chip->list);
|
|
|
|
if (chip->dev)
|
|
of_node_put(chip->dev->of_node);
|
|
|
|
irblasterchip_sysfs_unexport(chip);
|
|
mutex_unlock(&irblaster_lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(irblasterchip_remove);
|
|
|
|
static bool irblaster_ops_check(struct irblaster_ops *ops)
|
|
{
|
|
/* These one interfaces are the most basic of the irblaster */
|
|
if (ops->send)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* irblasterchip_add() - register a new irblaster Controller
|
|
* @chip: the irblaster chip to add
|
|
* @Returns: 0 on success or a negative error code on failure.
|
|
*/
|
|
int irblasterchip_add(struct irblaster_chip *chip)
|
|
{
|
|
if (!chip || !chip->dev || !chip->ops)
|
|
return -EINVAL;
|
|
|
|
if (!irblaster_ops_check(chip->ops))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&irblaster_lock);
|
|
atomic_set(&chip->request, IRBLASTER_EXPORTED);
|
|
INIT_LIST_HEAD(&chip->list);
|
|
list_add(&chip->list, &irblaster_chips);
|
|
irblasterchip_sysfs_export(chip);
|
|
mutex_unlock(&irblaster_lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(irblasterchip_add);
|
|
|
|
/**
|
|
* irblaster_put() - release a irblaster controller
|
|
* @chip: irblaster controller
|
|
*/
|
|
void irblaster_put(struct irblaster_chip *chip)
|
|
{
|
|
if (!chip)
|
|
return;
|
|
|
|
mutex_lock(&irblaster_lock);
|
|
atomic_set(&chip->request, IRBLASTER_EXPORTED);
|
|
irblaster_chip_data_clear(chip);
|
|
irblasterchip_sysfs_export(chip);
|
|
mutex_unlock(&irblaster_lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(irblaster_put);
|
|
|
|
static struct irblaster_chip *of_node_to_irblasterchip(struct device_node *np)
|
|
{
|
|
struct irblaster_chip *chip;
|
|
|
|
mutex_lock(&irblaster_lock);
|
|
list_for_each_entry(chip, &irblaster_chips, list)
|
|
if (chip->dev && chip->dev->of_node == np) {
|
|
mutex_unlock(&irblaster_lock);
|
|
if (atomic_read(&chip->request) == IRBLASTER_REQUESTED)
|
|
return ERR_PTR(-EPROBE_DEFER);
|
|
return chip;
|
|
}
|
|
|
|
mutex_unlock(&irblaster_lock);
|
|
return ERR_PTR(-EPROBE_DEFER);
|
|
}
|
|
|
|
static int irblaster_set_default_state(struct irblaster_chip *pc,
|
|
const struct of_phandle_args *args)
|
|
{
|
|
int ret;
|
|
|
|
if (pc->of_irblaster_n_cells < 2 ||
|
|
args->args[0] <= 0 || args->args[1] > 100)
|
|
return -EINVAL;
|
|
|
|
pc->state.freq = args->args[0];
|
|
pc->state.duty = args->args[1];
|
|
|
|
ret = irblaster_set_freq(pc, pc->state.freq);
|
|
if (ret)
|
|
return -EINVAL;
|
|
|
|
ret = irblaster_set_duty(pc, pc->state.duty);
|
|
if (ret)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* of_irblaster_get() - request a irblaster via the irblaster framework
|
|
* @np: device node to get the irblaster from
|
|
* @con_id: consumer name
|
|
*
|
|
* Returns the irblaster controller parsed from the phandle and index
|
|
* specified in the "irblaster-config" property of a device tree node
|
|
* or a negative error-code on failure. Values parsed from the device
|
|
* tree are stored in the returned irblaster device object.
|
|
*
|
|
* Returns: A pointer to the requested irblaster controller or an ERR_PTR()
|
|
* -encoded error code on failure.
|
|
*/
|
|
struct irblaster_chip *of_irblaster_get(struct device_node *np,
|
|
const char *con_id)
|
|
{
|
|
struct of_phandle_args args;
|
|
struct irblaster_chip *pc;
|
|
int err, index = 0;
|
|
|
|
err = of_parse_phandle_with_args(np, "irblaster-config",
|
|
"#irblaster-cells", index,
|
|
&args);
|
|
if (err) {
|
|
pr_err("%s(): can't parse \"irblaster-config\" property\n",
|
|
__func__);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
pc = of_node_to_irblasterchip(args.np);
|
|
if (IS_ERR(pc)) {
|
|
pr_err("%s(): irblaster chip not found\n", __func__);
|
|
pc = ERR_PTR(-EINVAL);
|
|
goto put;
|
|
}
|
|
|
|
if (args.args_count != pc->of_irblaster_n_cells) {
|
|
pr_err("%s: wrong #irblaster-cells for %s\n", np->full_name,
|
|
args.np->full_name);
|
|
pc = ERR_PTR(-EINVAL);
|
|
goto put;
|
|
}
|
|
|
|
err = irblaster_set_default_state(pc, &args);
|
|
if (err < 0) {
|
|
pr_err("%s(): irblaster get state fail\n", __func__);
|
|
pc = ERR_PTR(-EINVAL);
|
|
goto put;
|
|
}
|
|
|
|
atomic_set(&pc->request, IRBLASTER_REQUESTED);
|
|
irblasterchip_sysfs_unexport(pc);
|
|
put:
|
|
of_node_put(args.np);
|
|
|
|
return pc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(of_irblaster_get);
|
|
|
|
static void devm_irblaster_release(struct device *dev, void *res)
|
|
{
|
|
irblaster_put(*(struct irblaster_chip **)res);
|
|
}
|
|
|
|
/**
|
|
* devm_of_irblaster_get() - resource managed of_irblaster_get()
|
|
* @dev: device for irblaster consumer
|
|
* @np: device node to get the irblaster from
|
|
* @con_id: consumer name
|
|
*
|
|
* This function performs like of_irblaster_get() but the acquired irblaster
|
|
* device will automatically be released on driver detach.
|
|
*
|
|
* Returns: A pointer to the requested irblaster device or an ERR_PTR()-encoded
|
|
* error code on failure.
|
|
*/
|
|
struct irblaster_chip *devm_of_irblaster_get(struct device *dev,
|
|
struct device_node *np,
|
|
const char *con_id)
|
|
{
|
|
struct irblaster_chip **dr, *chip;
|
|
|
|
dr = devres_alloc(devm_irblaster_release, sizeof(*dr), GFP_KERNEL);
|
|
if (!dr)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
chip = of_irblaster_get(np, con_id);
|
|
if (!IS_ERR(chip)) {
|
|
*dr = chip;
|
|
devres_add(dev, dr);
|
|
} else {
|
|
devres_free(dr);
|
|
}
|
|
|
|
return chip;
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_of_irblaster_get);
|