/* * acx00-core.c -- Device access for allwinnertech acx00 * * Copyright (C) 2017 Allwinner. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include /*#include */ #include #define SUNXI_CHIP_NAME "ACX00-CHIP" static unsigned int twi_id; atomic_t acx00_en; struct regmap_config acx00_base_regmap_config = { .reg_bits = 8, .val_bits = 16, }; static struct regulator *vcc_ave; static char key_name[20] = "ac200"; static char ave_regulator_name[20] = "vcc-audio-33"; static int tv_twi_used; static int sys_script_get_item(char *main_name, char *sub_name, int value[], int type); /** * acx00_reg_read: Read a single ACX00 register. * * @acx00: Device to read from. * @reg: Register to read. */ int acx00_reg_read(struct acx00 *acx00, unsigned short reg) { unsigned int val; int ret; mutex_lock(&acx00->lock); regmap_write(acx00->regmap, 0xfe, reg>>8); ret = regmap_read(acx00->regmap, reg&0xff, &val); mutex_unlock(&acx00->lock); if (ret < 0) return ret; else return val; } EXPORT_SYMBOL_GPL(acx00_reg_read); /** * acx00_reg_write: Write a single ACX00 register. * * @acx00: Device to write to. * @reg: Register to write to. * @val: Value to write. */ int acx00_reg_write(struct acx00 *acx00, unsigned short reg, unsigned short val) { int ret; mutex_lock(&acx00->lock); regmap_write(acx00->regmap, 0xfe, reg>>8); ret = regmap_write(acx00->regmap, reg&0xff, val); mutex_unlock(&acx00->lock); return ret; } EXPORT_SYMBOL_GPL(acx00_reg_write); int acx00_enable(void) { return atomic_read(&acx00_en); } EXPORT_SYMBOL_GPL(acx00_enable); #if defined(CONFIG_ARCH_SUN50IW6) /** * @name acx00_read_sid * @brief read tv out sid from efuse * @param[IN] none * @param[OUT] p_bang_gap:tve band gap * @return return 0 if success,-1 if fail */ static s32 acx00_read_sid(u16 *p_band_gap) { s32 ret = 0; u8 buf[6]; if (p_band_gap == NULL) { pr_err("%s's pointer type args are NULL!\n", __func__); return -1; } ret = sunxi_efuse_readn(EFUSE_OEM_NAME, buf, 6); if (ret < 0) { pr_err("sunxi_efuse_readn failed:%d\n", ret); return ret; } *p_band_gap = buf[4] + (buf[5] << 8); return 0; } #endif static void acx00_init_work(struct work_struct *work) { struct acx00 *acx00 = container_of(work, struct acx00, init_work); #if defined(CONFIG_ARCH_SUN50IW6) u16 band_gap = 0; #endif atomic_set(&acx00_en, 0); pr_err("%s,l:%d\n", __func__, __LINE__); #if defined(CONFIG_ARCH_SUN50IW6) acx00_read_sid(&band_gap); acx00_reg_write(acx00, 0x0050, band_gap | 0x8000 | (0xa << 6)); msleep(120); #endif /*endif CONFIG_ARCH_SUN50IW6 */ acx00_reg_write(acx00, 0x0002, 0x1); pr_err("%s,l:%d\n", __func__, __LINE__); atomic_set(&acx00_en, 1); } static struct mfd_cell acx00_devs[] = { { .name = "acx00-codec", }, { .name = "rtc0_ac200", }, { .name = "tv", }, { .name = "acx-ephy", }, }; /* * Instantiate the generic non-control parts of the device. */ static int acx00_device_init(struct acx00 *acx00, int irq) { int ret; dev_set_drvdata(acx00->dev, acx00); ret = mfd_add_devices(acx00->dev, -1, acx00_devs, ARRAY_SIZE(acx00_devs), NULL, 0, NULL); if (ret != 0) { dev_err(acx00->dev, "Failed to add children: %d\n", ret); goto err; } return 0; err: mfd_remove_devices(acx00->dev); return ret; } static void acx00_device_exit(struct acx00 *acx00) { mfd_remove_devices(acx00->dev); } /**************************read reg interface**************************/ static ssize_t acx00_dump_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { static int val; int reg, num, i = 0; int value_r[128]; s32 ret = -1; struct acx00 *acx00 = dev_get_drvdata(dev); ret = kstrtoint(buf, 16, &val); if (!ret) { dev_err(dev, "kstrtoint fail!\n"); return ret; } reg = (val >> 8); num = val & 0xff; dev_info(dev, "\n"); dev_info(dev, "read:start add:0x%x,count:0x%x\n", reg, num); do { value_r[i] = acx00_reg_read(acx00, reg); dev_info(dev, "0x%x: 0x%04x ", reg, value_r[i]); reg += 1; i++; if (i == num) dev_info(dev, "\n"); if (i % 4 == 0) dev_info(dev, "\n"); } while (i < num); return count; } static ssize_t acx00_dump_show(struct device *dev, struct device_attribute *attr, char *buf) { dev_info(dev, "echo reg|count > dump\n"); dev_info(dev, "eg read star address=0x0006,count 0x10:echo 0x610 > dump\n"); dev_info(dev, "eg read star address=0x2000,count 0x10:echo 0x200010 > dump\n"); return 0; } /**************************write reg interface**************************/ static ssize_t acx00_write_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { static int val; int reg; int value_w; s32 ret = -1; struct acx00 *acx00 = dev_get_drvdata(dev); ret = kstrtoint(buf, 16, &val); if (!ret) { dev_err(dev, "kstrtoint fail!\n"); return ret; } reg = (val >> 16); value_w = val & 0xFFFF; acx00_reg_write(acx00, reg, value_w); dev_info(dev, "write 0x%x to reg:0x%x\n", value_w, reg); return count; } static ssize_t acx00_write_show(struct device *dev, struct device_attribute *attr, char *buf) { dev_info(dev, "echo reg|val > write\n"); printk( KERN_INFO "eg write value:0x13fe to address:0x0004 :echo 0x413fe > write\n"); printk( KERN_INFO "eg write value:0x6 to address:0x2000 :echo 0x20000006 > write\n"); return 0; } static DEVICE_ATTR(dump, 0644, acx00_dump_show, acx00_dump_store); static DEVICE_ATTR(write, 0644, acx00_write_show, acx00_write_store); static struct attribute *audio_debug_attrs[] = { &dev_attr_dump.attr, &dev_attr_write.attr, NULL, }; static struct attribute_group audio_debug_attr_group = { .name = "acx00_debug", .attrs = audio_debug_attrs, }; static int acx00_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct acx00 *acx00; int ret = 0; int value; pr_err("%s,l:%d\n", __func__, __LINE__); acx00 = devm_kzalloc(&i2c->dev, sizeof(struct acx00), GFP_KERNEL); if (acx00 == NULL) return -ENOMEM; i2c_set_clientdata(i2c, acx00); acx00->dev = &i2c->dev; acx00->irq = i2c->irq; mutex_init(&acx00->lock); acx00->regmap = devm_regmap_init_i2c(i2c, &acx00_base_regmap_config); if (IS_ERR(acx00->regmap)) { ret = PTR_ERR(acx00->regmap); dev_err(acx00->dev, "Failed to allocate register map: %d\n", ret); return ret; } ret = sysfs_create_group(&i2c->dev.kobj, &audio_debug_attr_group); if (ret) pr_err("failed to create attr group\n"); INIT_WORK(&acx00->init_work, acx00_init_work); ret = sys_script_get_item(key_name, "tv_pwm_ch", &value, 1); if (ret == 1) { acx00->pwm_ac200 = pwm_request(value, NULL); if (!IS_ERR_OR_NULL(acx00->pwm_ac200)) { pwm_config(acx00->pwm_ac200, 20, 41); pwm_enable(acx00->pwm_ac200); } else dev_warn(acx00->dev, "Warn: can't get pwm device\n"); } else { dev_warn(acx00->dev, "Get tv_pwm_ch failed\n"); } atomic_set(&acx00_en, 0); schedule_work(&acx00->init_work); return acx00_device_init(acx00, i2c->irq); } static int acx00_i2c_remove(struct i2c_client *i2c) { struct acx00 *acx00 = i2c_get_clientdata(i2c); sysfs_remove_group(&i2c->dev.kobj, &audio_debug_attr_group); acx00_device_exit(acx00); if (!IS_ERR_OR_NULL(acx00->pwm_ac200)) { pwm_disable(acx00->pwm_ac200); pwm_free(acx00->pwm_ac200); } return 0; } static int acx00_i2c_suspend(struct device *dev) { struct acx00 *acx00 = dev_get_drvdata(dev); if (vcc_ave != NULL) { regulator_disable(vcc_ave); regulator_put(vcc_ave); vcc_ave = NULL; } if (!IS_ERR_OR_NULL(acx00->pwm_ac200)) pwm_disable(acx00->pwm_ac200); atomic_set(&acx00_en, 0); return 0; } static int acx00_i2c_resume(struct device *dev) { int ret = 0; struct acx00 *acx00 = dev_get_drvdata(dev); #if defined(CONFIG_ARCH_SUN50IW6) u16 band_gap = 0; #endif vcc_ave = regulator_get(NULL, ave_regulator_name); if (IS_ERR_OR_NULL(vcc_ave)) { pr_err("get audio %s failed\n", ave_regulator_name); } else { ret = regulator_enable(vcc_ave); if (IS_ERR(vcc_ave)) { pr_err("[%s]: vcc_ave:regulator_enable() failed!\n", __func__); } } if (!IS_ERR_OR_NULL(acx00->pwm_ac200)) { pwm_config(acx00->pwm_ac200, 20, 41); pwm_enable(acx00->pwm_ac200); } atomic_set(&acx00_en, 0); msleep(40); pr_err("%s,l:%d\n", __func__, __LINE__); #if defined(CONFIG_ARCH_SUN50IW6) if (acx00_read_sid(&band_gap) == 0 && band_gap != 0) acx00_reg_write(acx00, 0x0050, band_gap | 0x8000 | (0xa << 6)); #endif /*endif CONFIG_ARCH_SUN50IW6 */ acx00_reg_write(acx00, 0x0002, 0x1); pr_err("%s,l:%d\n", __func__, __LINE__); atomic_set(&acx00_en, 1); return 0; } static const struct dev_pm_ops acx00_core_pm_ops = { .suspend_late = acx00_i2c_suspend, .resume_early = acx00_i2c_resume, }; static int acx00_detect(struct i2c_client *client, struct i2c_board_info *info) { struct i2c_adapter *adapter = client->adapter; pr_info("%s, l:%d, twi_id:%d, adapter->nr:%d\n", __func__, __LINE__, twi_id, adapter->nr); if (twi_id == adapter->nr) { strlcpy(info->type, SUNXI_CHIP_NAME, I2C_NAME_SIZE); return 0; } else { return -ENODEV; } } static unsigned short normal_i2c[] = {0x10, I2C_CLIENT_END}; static const struct i2c_device_id acx00_id[] = { {"ACX00-CHIP", 2}, {} }; static const struct of_device_id sunxi_ac200_match[] = { { .compatible = "allwinner,sunxi-ac200", }, {}, }; static struct i2c_driver acx00_i2c_driver = { .class = I2C_CLASS_HWMON, .id_table = acx00_id, .probe = acx00_i2c_probe, .remove = acx00_i2c_remove, .driver = { .owner = THIS_MODULE, .name = "ACX00-CHIP", .of_match_table = sunxi_ac200_match, .pm = &acx00_core_pm_ops, }, .address_list = normal_i2c, }; /* type: 0:invalid, 1: int; 2:str*/ static int sys_script_get_item(char *main_name, char *sub_name, int value[], int type) { char compat[32]; u32 len = 0; struct device_node *node; int ret = 0; len = sprintf(compat, "allwinner,sunxi-%s", main_name); if (len > 32) { pr_warn("size of mian_name is out of range\n"); goto error_exit; } node = of_find_compatible_node(NULL, NULL, compat); if (!node) { pr_warn("of_find_compatible_node %s fail\n", compat); goto error_exit; } if (type == 1) { if (of_property_read_u32_array(node, sub_name, value, 1)) { pr_info("of_property_read_u32_array %s.%s fail\n", main_name, sub_name); goto error_exit; } else ret = type; } else if (type == 2) { const char *str; if (of_property_read_string(node, sub_name, &str)) { pr_info("of_property_read_string %s.%s fail\n", main_name, sub_name); goto error_exit; } else { ret = type; memcpy((void *)value, str, strlen(str) + 1); } } return ret; error_exit: return -1; } static int __init acx00_i2c_init(void) { int ret = 0; int value; int ac200_used = 0; ret = sys_script_get_item(key_name, "tv_used", &value, 1); if (ret == 1) ac200_used = value; if (ac200_used) { ret = sys_script_get_item(key_name, "tv_twi_used", &value, 1); if (ret == 1) tv_twi_used = value; if (tv_twi_used == 1) { ret = sys_script_get_item(key_name, "tv_twi_id", &value, 1); twi_id = (ret == 1) ? value : twi_id; ret = sys_script_get_item(key_name, "tv_twi_addr", &value, 1); normal_i2c[0] = (ret == 1) ? value : normal_i2c[0]; acx00_i2c_driver.detect = acx00_detect; ret = i2c_add_driver(&acx00_i2c_driver); if (ret != 0) pr_err( "Failed to register acx00 I2C driver: %d\n", ret); } ret = sys_script_get_item(key_name, "tv_regulator_name", (int *)&ave_regulator_name, 2); if (ret == 2) { vcc_ave = regulator_get(NULL, ave_regulator_name); if (IS_ERR_OR_NULL(vcc_ave)) { pr_err("get audio %s failed\n", ave_regulator_name); } else { ret = regulator_enable(vcc_ave); if (IS_ERR(vcc_ave)) { pr_err( "[%s]: vcc_ave enable failed!\n", __func__); } } } else { pr_err("get ave_regulator_name failed!\n"); } } return ret; } subsys_initcall_sync(acx00_i2c_init); static void __exit acx00_i2c_exit(void) { i2c_del_driver(&acx00_i2c_driver); } module_exit(acx00_i2c_exit); MODULE_DESCRIPTION("Core support for the ACX00X00 audio CODEC"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("huangxin");