Files

344 lines
8.3 KiB
C

/*
* Copyright (c) XMEDIA. All rights reserved.
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/devfreq.h>
#include "base.h"
#include "osal_devfreq.h"
static OSAL_LIST_HEAD(media_list);
static DEFINE_MUTEX(media_sem);
/*
* Assigned numbers, used for dynamic minors
*/
#define DYNAMIC_MINORS 64 /* like dynamic majors */
static unsigned char media_minors[DYNAMIC_MINORS / 8];
int media_devfreq_target(struct device *dev, unsigned long *freq, unsigned int flags)
{
struct media_device *pdev = to_media_device(dev);
struct media_driver *pdrv = to_media_driver(dev->driver);
if ((pdrv->ops == NULL) || (pdrv->ops->devfreq_target == NULL)) {
return 0;
}
return pdrv->ops->devfreq_target(pdev, freq, flags);
}
int media_devfreq_get_dev_status(struct device *dev, struct devfreq_dev_status *stat)
{
struct media_device *pdev = to_media_device(dev);
struct media_driver *pdrv = to_media_driver(dev->driver);
osal_devfreq_dev_status_t pstat;
if ((pdrv->ops == NULL) || (pdrv->ops->devfreq_get_dev_status == NULL)) {
return 0;
}
pstat.total_time = stat->total_time;
pstat.busy_time = stat->busy_time;
pstat.current_frequency = stat->current_frequency;
pstat.private_data = stat->private_data;
return pdrv->ops->devfreq_get_dev_status(pdev, &pstat);
}
int media_devfreq_get_cur_freq(struct device *dev, unsigned long *freq)
{
struct media_device *pdev = to_media_device(dev);
struct media_driver *pdrv = to_media_driver(dev->driver);
if ((pdrv->ops == NULL) || (pdrv->ops->devfreq_get_cur_freq == NULL)) {
return 0;
}
return pdrv->ops->devfreq_get_cur_freq(pdev, freq);
}
void media_devfreq_exit(struct device *dev)
{
struct media_device *pdev = to_media_device(dev);
struct media_driver *pdrv = to_media_driver(dev->driver);
if ((pdrv->ops == NULL) || (pdrv->ops->devfreq_exit == NULL)) {
return;
}
return pdrv->ops->devfreq_exit(pdev);
}
int media_devfreq_register(struct media_device *media, osal_devfreq_para_t *devfreq_para)
{
struct device *dev = (struct device *)&media->device;
struct devfreq_dev_profile *profile = &(media->profile);
profile->initial_freq = devfreq_para->initial_freq;
profile->polling_ms = devfreq_para->polling_ms;
profile->target = media_devfreq_target;
profile->get_dev_status = media_devfreq_get_dev_status;
profile->get_cur_freq = media_devfreq_get_cur_freq;
profile->exit = media_devfreq_exit;
profile->freq_table = devfreq_para->freq_table;
profile->max_state = devfreq_para->max_state;
media->devfreq = devm_devfreq_add_device(dev, profile, "userspace", NULL);
if (IS_ERR(media->devfreq)) {
printk("failed to add devfreq device\n");
return -1;
}
return 0;
}
void media_devfreq_unregister(struct media_device *media)
{
struct device *dev = (struct device *)&media->device;
devm_devfreq_remove_device(dev, media->devfreq);
return;
}
static int media_open(struct inode *inode, struct file *file)
{
int minor = iminor(inode);
struct media_device *c = NULL;
int err = -ENODEV;
const struct file_operations *old_fops, *new_fops = NULL;
mutex_lock(&media_sem);
osal_list_for_each_entry(c, &media_list, list) {
if (c->minor == minor) {
new_fops = fops_get(c->fops);
break;
}
}
if (new_fops == NULL) {
mutex_unlock(&media_sem);
request_module("char-major-%d-%d", MEDIA_DEVICE_MAJOR, minor);
mutex_lock(&media_sem);
osal_list_for_each_entry(c, &media_list, list) {
if (c->minor == minor) {
new_fops = fops_get(c->fops);
break;
}
}
if (new_fops == NULL) {
goto fail;
}
}
err = 0;
old_fops = file->f_op;
file->f_op = new_fops;
if (file->f_op->open) {
file->private_data = c;
err = file->f_op->open(inode, file);
if (err) {
fops_put(file->f_op);
file->private_data = NULL;
file->f_op = fops_get(old_fops);
}
}
fops_put(old_fops);
fail:
mutex_unlock(&media_sem);
return err;
}
static struct file_operations media_fops = {
.owner = THIS_MODULE,
.open = media_open,
};
/**
* media_register - register a media device
* @media: device structure
*
* Register a media device with the kernel. If the minor
* number is set to %MEDIA_DYNAMIC_MINOR a minor number is assigned
* and placed in the minor field of the structure. For other cases
* the minor number requested is used.
*
* The structure passed is linked into the kernel and may not be
* destroyed until it has been unregistered.
*
* A zero is returned on success and a negative errno code for
* failure.
*/
int media_register(struct media_device *media)
{
struct media_device *ptmp = NULL;
struct media_driver *pdrv = NULL;
int err = 0;
mutex_lock(&media_sem);
/* check if registered */
osal_list_for_each_entry(ptmp, &media_list, list) {
if (ptmp->minor == media->minor) {
mutex_unlock(&media_sem);
return -EBUSY;
}
}
/* check minor */
if (media->minor == MEDIA_DYNAMIC_MINOR) {
int i = DYNAMIC_MINORS;
while (--i >= 0)
if ((media_minors[(unsigned int)i >> 3] & (1 << ((unsigned int)i & 7))) == 0) {
break;
}
if (i < 0) {
mutex_unlock(&media_sem);
return -EBUSY;
}
media->minor = i;
}
if (media->minor < DYNAMIC_MINORS) {
media_minors[media->minor >> 3] |= 1 << (media->minor & 7);
}
/* device register */
err = _media_device_register(media);
if (err < 0) {
media_minors[media->minor >> 3] &= ~(1 << (media->minor & 7));
goto out;
}
/* driver register */
pdrv = media_driver_register(media->devfs_name, media->owner, media->drvops);
if (IS_ERR(pdrv)) {
_media_device_unregister(media);
media_minors[media->minor >> 3] &= ~(1 << (media->minor & 7));
err = PTR_ERR(pdrv);
goto out;
}
media->driver = pdrv;
/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
osal_list_add(&media->list, &media_list);
out:
mutex_unlock(&media_sem);
return err;
}
EXPORT_SYMBOL(media_register);
/**
* media_unregister - unregister a media device
* @media: device to unregister
*
* Unregister a media device that was previously
* successfully registered with media_register(). Success
* is indicated by a zero return, a negative errno code
* indicates an error.
*/
int media_unregister(struct media_device *media)
{
struct media_device *ptmp = NULL, *_ptmp = NULL;
if (osal_list_empty(&media->list)) {
return -EINVAL;
}
mutex_lock(&media_sem);
osal_list_for_each_entry_safe(ptmp, _ptmp, &media_list, list) {
/* if found, unregister device & driver */
if (ptmp->minor == media->minor) {
osal_list_del(&media->list);
media_driver_unregister(media->driver);
media->driver = NULL;
_media_device_unregister(media);
media_minors[media->minor >> 3] &= ~(1 << (media->minor & 7));
break;
}
}
mutex_unlock(&media_sem);
return 0;
}
EXPORT_SYMBOL(media_unregister);
int media_init(void)
{
int ret;
// 1
ret = media_bus_init();
if (ret) {
goto err0;
}
// 2
ret = -EIO;
if (register_chrdev(MEDIA_DEVICE_MAJOR, "media", &media_fops)) {
goto err1;
}
printk("Module media: init ok\n");
return 0;
// 3
err1:
media_bus_exit();
err0:
return ret;
}
void media_exit(void)
{
// 0
if (!osal_list_empty(&media_list)) {
printk("!!! Module media: sub module in list\n");
return;
}
unregister_chrdev(MEDIA_DEVICE_MAJOR, "media");
media_bus_exit();
printk("!!! Module media: exit ok\n");
}