sc27xx-vibra.c 3.79 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2018 Spreadtrum Communications Inc.
 */

#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/input.h>
#include <linux/workqueue.h>

#define CUR_DRV_CAL_SEL		GENMASK(13, 12)
#define SLP_LDOVIBR_PD_EN	BIT(9)
#define LDO_VIBR_PD		BIT(8)

struct vibra_info {
	struct input_dev	*input_dev;
	struct work_struct	play_work;
	struct regmap		*regmap;
	u32			base;
	u32			strength;
	bool			enabled;
};

static void sc27xx_vibra_set(struct vibra_info *info, bool on)
{
	if (on) {
		regmap_update_bits(info->regmap, info->base, LDO_VIBR_PD, 0);
		regmap_update_bits(info->regmap, info->base,
				   SLP_LDOVIBR_PD_EN, 0);
		info->enabled = true;
	} else {
		regmap_update_bits(info->regmap, info->base, LDO_VIBR_PD,
				   LDO_VIBR_PD);
		regmap_update_bits(info->regmap, info->base,
				   SLP_LDOVIBR_PD_EN, SLP_LDOVIBR_PD_EN);
		info->enabled = false;
	}
}

static int sc27xx_vibra_hw_init(struct vibra_info *info)
{
	return regmap_update_bits(info->regmap, info->base, CUR_DRV_CAL_SEL, 0);
}

static void sc27xx_vibra_play_work(struct work_struct *work)
{
	struct vibra_info *info = container_of(work, struct vibra_info,
					       play_work);

	if (info->strength && !info->enabled)
		sc27xx_vibra_set(info, true);
	else if (info->strength == 0 && info->enabled)
		sc27xx_vibra_set(info, false);
}

static int sc27xx_vibra_play(struct input_dev *input, void *data,
			     struct ff_effect *effect)
{
	struct vibra_info *info = input_get_drvdata(input);

	info->strength = effect->u.rumble.weak_magnitude;
	schedule_work(&info->play_work);

	return 0;
}

static void sc27xx_vibra_close(struct input_dev *input)
{
	struct vibra_info *info = input_get_drvdata(input);

	cancel_work_sync(&info->play_work);
	if (info->enabled)
		sc27xx_vibra_set(info, false);
}

static int sc27xx_vibra_probe(struct platform_device *pdev)
{
	struct vibra_info *info;
	int error;

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

	info->regmap = dev_get_regmap(pdev->dev.parent, NULL);
	if (!info->regmap) {
		dev_err(&pdev->dev, "failed to get vibrator regmap.\n");
		return -ENODEV;
	}

	error = device_property_read_u32(&pdev->dev, "reg", &info->base);
	if (error) {
		dev_err(&pdev->dev, "failed to get vibrator base address.\n");
		return error;
	}

	info->input_dev = devm_input_allocate_device(&pdev->dev);
	if (!info->input_dev) {
		dev_err(&pdev->dev, "failed to allocate input device.\n");
		return -ENOMEM;
	}

	info->input_dev->name = "sc27xx:vibrator";
	info->input_dev->id.version = 0;
	info->input_dev->close = sc27xx_vibra_close;

	input_set_drvdata(info->input_dev, info);
	input_set_capability(info->input_dev, EV_FF, FF_RUMBLE);
	INIT_WORK(&info->play_work, sc27xx_vibra_play_work);
	info->enabled = false;

	error = sc27xx_vibra_hw_init(info);
	if (error) {
		dev_err(&pdev->dev, "failed to initialize the vibrator.\n");
		return error;
	}

	error = input_ff_create_memless(info->input_dev, NULL,
					sc27xx_vibra_play);
	if (error) {
		dev_err(&pdev->dev, "failed to register vibrator to FF.\n");
		return error;
	}

	error = input_register_device(info->input_dev);
	if (error) {
		dev_err(&pdev->dev, "failed to register input device.\n");
		return error;
	}

	return 0;
}

static const struct of_device_id sc27xx_vibra_of_match[] = {
	{ .compatible = "sprd,sc2731-vibrator", },
	{}
};
MODULE_DEVICE_TABLE(of, sc27xx_vibra_of_match);

static struct platform_driver sc27xx_vibra_driver = {
	.driver = {
		.name = "sc27xx-vibrator",
		.of_match_table = sc27xx_vibra_of_match,
	},
	.probe = sc27xx_vibra_probe,
};

module_platform_driver(sc27xx_vibra_driver);

MODULE_DESCRIPTION("Spreadtrum SC27xx Vibrator Driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>");