振动 芯片twl4030 linux 驱动程序

/*

  • twl4030-vibra.c - TWL4030 Vibrator driver
  • Copyright © 2008-2010 Nokia Corporation
  • 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.
  • 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.
  • You should have received a copy of the GNU General Public License
  • along with this program; if not, write to the Free Software
  • Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  • 02110-1301 USA

*/

#include <linux/module.h>

#include <linux/jiffies.h>

#include <linux/platform_device.h>

#include <linux/of.h>

#include <linux/workqueue.h>

#include <linux/mfd/twl.h>

#include <linux/mfd/twl4030-audio.h>

#include <linux/input.h>

#include <linux/slab.h>

/* MODULE ID2 */

#define LEDEN 0x00

/* ForceFeedback /
#define EFFECT_DIR_180_DEG 0x8000 /
range is 0 - 0xFFFF */

struct vibra_info {

struct device *dev;

struct input_dev *input_dev;

struct work_struct	play_work;

bool			enabled;
int			speed;
int			direction;

bool			coexist;

};

static void vibra_disable_leds(void)

{

u8 reg;

/* Disable LEDA & LEDB, cannot be used with vibra (PWM) */
twl_i2c_read_u8(TWL4030_MODULE_LED, &reg, LEDEN);
reg &= ~0x03;
twl_i2c_write_u8(TWL4030_MODULE_LED, LEDEN, reg);

}

/* Powers H-Bridge and enables audio clk */

static void vibra_enable(struct vibra_info *info)

{

u8 reg;

twl4030_audio_enable_resource(TWL4030_AUDIO_RES_POWER);

/* turn H-Bridge on */
twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE,
		&reg, TWL4030_REG_VIBRA_CTL);
twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
		 (reg | TWL4030_VIBRA_EN), TWL4030_REG_VIBRA_CTL);

twl4030_audio_enable_resource(TWL4030_AUDIO_RES_APLL);

info->enabled = true;

}

static void vibra_disable(struct vibra_info *info)

{

u8 reg;

/* Power down H-Bridge */
twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE,
		&reg, TWL4030_REG_VIBRA_CTL);
twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
		 (reg & ~TWL4030_VIBRA_EN), TWL4030_REG_VIBRA_CTL);

twl4030_audio_disable_resource(TWL4030_AUDIO_RES_APLL);
twl4030_audio_disable_resource(TWL4030_AUDIO_RES_POWER);

info->enabled = false;

}

static void vibra_play_work(struct work_struct *work)

{

struct vibra_info *info = container_of(work,

struct vibra_info, play_work);

int dir;

int pwm;

u8 reg;

dir = info->direction;
pwm = info->speed;

twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE,
		&reg, TWL4030_REG_VIBRA_CTL);
if (pwm && (!info->coexist || !(reg & TWL4030_VIBRA_SEL))) {

	if (!info->enabled)
		vibra_enable(info);

	/* set vibra rotation direction */
	twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE,
			&reg, TWL4030_REG_VIBRA_CTL);
	reg = (dir) ? (reg | TWL4030_VIBRA_DIR) :
		(reg & ~TWL4030_VIBRA_DIR);
	twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
			 reg, TWL4030_REG_VIBRA_CTL);

	/* set PWM, 1 = max, 255 = min */
	twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
			 256 - pwm, TWL4030_REG_VIBRA_SET);
} else {
	if (info->enabled)
		vibra_disable(info);
}

}

/*** Input/ForceFeedback ***/

static int vibra_play(struct input_dev *input, void *data,

struct ff_effect *effect)

{

struct vibra_info *info = input_get_drvdata(input);

info->speed = effect->u.rumble.strong_magnitude >> 8;
if (!info->speed)
	info->speed = effect->u.rumble.weak_magnitude >> 9;
info->direction = effect->direction < EFFECT_DIR_180_DEG ? 0 : 1;
schedule_work(&info->play_work);
return 0;

}

static void twl4030_vibra_close(struct input_dev *input)

{

struct vibra_info *info = input_get_drvdata(input);

cancel_work_sync(&info->play_work);

if (info->enabled)
	vibra_disable(info);

}

/*** Module ***/

static int __maybe_unused twl4030_vibra_suspend(struct device *dev)

{

struct platform_device *pdev = to_platform_device(dev);

struct vibra_info *info = platform_get_drvdata(pdev);

if (info->enabled)
	vibra_disable(info);

return 0;

}

static int __maybe_unused twl4030_vibra_resume(struct device *dev)

{

vibra_disable_leds();

return 0;

}

static SIMPLE_DEV_PM_OPS(twl4030_vibra_pm_ops,

twl4030_vibra_suspend, twl4030_vibra_resume);

static bool twl4030_vibra_check_coexist(struct twl4030_vibra_data *pdata,

struct device_node *parent)

{

struct device_node *node;

if (pdata && pdata->coexist)
	return true;

node = of_get_child_by_name(parent, "codec");
if (node) {
	of_node_put(node);
	return true;
}

return false;

}

static int twl4030_vibra_probe(struct platform_device *pdev)

{

struct twl4030_vibra_data *pdata = dev_get_platdata(&pdev->dev);

struct device_node *twl4030_core_node = pdev->dev.parent->of_node;

struct vibra_info *info;

int ret;

if (!pdata && !twl4030_core_node) {
	dev_dbg(&pdev->dev, "platform_data not available\n");
	return -EINVAL;
}

info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
	return -ENOMEM;

info->dev = &pdev->dev;
info->coexist = twl4030_vibra_check_coexist(pdata, twl4030_core_node);
INIT_WORK(&info->play_work, vibra_play_work);

info->input_dev = devm_input_allocate_device(&pdev->dev);
if (info->input_dev == NULL) {
	dev_err(&pdev->dev, "couldn't allocate input device\n");
	return -ENOMEM;
}

input_set_drvdata(info->input_dev, info);

info->input_dev->name = "twl4030:vibrator";
info->input_dev->id.version = 1;
info->input_dev->close = twl4030_vibra_close;
__set_bit(FF_RUMBLE, info->input_dev->ffbit);

ret = input_ff_create_memless(info->input_dev, NULL, vibra_play);
if (ret < 0) {
	dev_dbg(&pdev->dev, "couldn't register vibrator to FF\n");
	return ret;
}

ret = input_register_device(info->input_dev);
if (ret < 0) {
	dev_dbg(&pdev->dev, "couldn't register input device\n");
	goto err_iff;
}

vibra_disable_leds();

platform_set_drvdata(pdev, info);
return 0;

err_iff:

input_ff_destroy(info->input_dev);

return ret;

}

static struct platform_driver twl4030_vibra_driver = {

.probe = twl4030_vibra_probe,

.driver = {

.name = "twl4030-vibra",

.pm = &twl4030_vibra_pm_ops,

},

};

module_platform_driver(twl4030_vibra_driver);

MODULE_ALIAS("platform:twl4030-vibra");

MODULE_DESCRIPTION("TWL4030 Vibra driver");

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Nokia Corporation");

相关推荐
明金同学1 小时前
腾讯云海外服务器Window切换为linux系统(从Window DD 到 Linux)
linux·服务器·腾讯云
CC大煊2 小时前
【Linux】vi/vim 使用技巧
linux·运维·vim
是十一月末2 小时前
Linux的基本功能和命令
linux·服务器·开发语言·数据库
暮已深3 小时前
【RTAB-Map+VINS-Fusion+euroc】(Ubuntu 20.04)三维稠密重建-实践笔记
linux·笔记·ubuntu
浮尘笔记3 小时前
在Ubuntu服务器上备份文件到自己的百度网盘
linux·服务器·ubuntu
Hacker_xingchen3 小时前
影响 Linux、Unix 系统的 CUPS 漏洞可导致 RCE
linux·运维·unix
难以触及的高度3 小时前
Unix/Linux 命令行重定向操作
linux·服务器·unix
neeef_se3 小时前
【Linux】WG-Easy:基于 Docker 和 Web 面板的异地组网
linux·前端·docker
都适、隶仁ミ3 小时前
【密码学】SM4算法
linux·运维·服务器·算法·网络安全·密码学·网络攻击模型
君逸~~4 小时前
RK3568(二)——字符设备驱动开发
linux·驱动开发·笔记·学习·rk3568