blob: dfc5b7897ff58e7e932dd037e900a6451be7ef17 [file] [log] [blame]
/*
* drivers/amlogic/irblaster/core.c
*
* Copyright (C) 2017 Amlogic, Inc. 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 as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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/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 % 2 == 1) || 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);